2017-09-20 30 views
6

Mam JSON z tablicą wartości:Jak odkodować tablicę wartości, których typy zależą od znacznika?

[ 
    { "tag": "Foo", … }, 
    { "tag": "Bar", … }, 
    { "tag": "Baz", … }, 
] 

Chcę dekodować tej tablicy do tablicy struct s gdzie szczególny rodzaj zależy od tagu:

protocol SomeCommonType {} 

struct Foo: Decodable, SomeCommonType { … } 
struct Bar: Decodable, SomeCommonType { … } 
struct Baz: Decodable, SomeCommonType { … } 

let values = try JSONDecoder().decode([SomeCommonType].self, from: …) 

jak mogę Zrób to? W tej chwili mam ten nieznacznie brzydki Wrapper:

struct DecodingWrapper: Decodable { 

    let value: SomeCommonType 

    public init(from decoder: Decoder) throws { 
     let c = try decoder.singleValueContainer() 
     if let decoded = try? c.decode(Foo.self) { 
      value = decoded 
     } else if let decoded = try? c.decode(Bar.self) { 
      value = decoded 
     } else if let decoded = try? c.decode(Baz.self) { 
      value = decoded 
     } else { 
      throw … 
     } 
    } 
} 

, a następnie:

let wrapped = try JSONDecoder().decode([DecodingWrapper].self, from: …) 
let values = wrapped.map { $0.value } 

jest jakiś lepszy sposób?

+0

jestem przy założeniu, że nie jesteś ten, który wysyła to json. Jedyny inny sposób, jaki mogę sobie wyobrazić, to przechowywanie słownika od napisu do typu, a następnie sprawdzanie, czy znacznik znajduje się w słowniku, jeśli tak się stanie, to otrzymasz odpowiedni typ, a następnie zainicjujesz obiekt. (Pominąłem kilka drobniejszych szczegółów, ale myślę, że macie główny pomysł). – TNguyen

+1

Porównaj drugą część https: // stackoverflow.com/a/44473156/2976878 dla innego podejścia (nie jestem pewien, czy uznałbyś to za lepsze, czy nie, nadal używa on typu wrappera) – Hamish

+0

@Hamish, to pytanie jest dokładnie tym, czego szukam, więc zamierzam to zamknąć jako dupek po wygaśnięciu nagrody. Dziękuję Ci! – zoul

Odpowiedz

2

może być enum może sprawić, że twój kod będzie trochę czystszy. Każdy przypadek będzie odpowiadał typowi (znacznikowi) twojego json. W zależności od przypadku przeanalizujesz swój json do odpowiedniego Modelu. W każdym razie powinna istnieć jakaś ewaluacja, którą model wybrać. Więc ja przyszedł na ten

protocol SomeCommonType {} 
protocol DecodableCustomType: Decodable, SomeCommonType {} 

struct Foo: DecodableCustomType {} 
struct Bar: DecodableCustomType {} 
struct Baz: DecodableCustomType {} 

enum ModelType: String { 
    case foo 
    case bar 
    case baz 

    var type: DecodableCustomType.Type { 
    switch self { 
    case .foo: return Foo.self 
    case .bar: return Bar.self 
    case .baz: return Baz.self 
    } 
    } 
} 

func decoder(json: JSON) { 
    let type = json["type"].stringValue 
    guard let modelType = ModelType(rawValue: type) else { return } 

    // here you can use modelType.type 
} 
+0

Co to jest typ 'JSON' w sygnaturze' dekodera'? Próbuję odszyfrować JSON używając "Codable" Swifta i to jest problem, ponieważ 'Codable' chce tego typu, aby rozszyfrować JSON (to jest catch 22). – zoul

0

Można również użyć słownika do mapowania:

protocol SomeCommonType {} 

struct Foo: Decodable, SomeCommonType { } 
struct Bar: Decodable, SomeCommonType { } 
struct Baz: Decodable, SomeCommonType { } 

let j: [[String:String]] = [ 
    ["tag": "Foo"], 
    ["tag": "Bar"], 
    ["tag": "Baz"], 
    ["tag": "Undefined type"], 
    ["missing": "tag"] 
] 

let mapping: [String: SomeCommonType.Type] = [ 
    "Foo": Foo.self, 
    "Bar": Bar.self, 
    "Baz": Baz.self 
] 

print(j.map { $0["tag"].flatMap { mapping[$0] } }) 
// [Optional(Foo), Optional(Bar), Optional(Baz), nil, nil] 

print(j.flatMap { $0["tag"].flatMap { mapping[$0] } }) 
// [Foo, Bar, Baz] 
+0

Jak można odczytać znacznik z JSON za pomocą 'Codable'? – zoul

3

Twoja tablica zawiera heterogenicznych obiektów skończonych, przeliczalnych odmian; brzmi jak idealny przypadek użycia do wyliczenia Swift. Nie nadaje się do polimorfizmu, ponieważ te "rzeczy" niekoniecznie są tego samego rodzaju, mówiąc konceptualnie. Po prostu zostały oznaczone.

Spójrz na to w ten sposób: masz szereg rzeczy, które wszystkie mają znaczniki, niektóre są tego rodzaju, inne są zupełnie innego rodzaju, a jeszcze inne ... a czasami nawet nie rozpoznajesz etykietka. Wyliczenie Swift to idealny pojazd do uchwycenia tego pomysłu.

Więc masz kilka elemencie, który współdzieli właściwość tag ale poza tym całkowicie różnią się od siebie:

struct Foo: Decodable { 
    let tag: String 
    let fooValue: Int 
} 

struct Bar: Decodable { 
    let tag: String 
    let barValue: Int 
} 

struct Baz: Decodable { 
    let tag: String 
    let bazValue: Int 
} 

A twoja tablica może zawierać dowolną instancję powyższych typów, lub nieznanego typu. Więc masz enum TagggedThing (lub lepsze imię).

enum TagggedThing { 
    case foo(Foo) 
    case bar(Bar) 
    case baz(Baz) 
    case unknown 
} 

Twoja tablica w kategoriach Swift ma typ [TagggedThing]. Więc zgodne typ TagggedThing do Decodable tak:

extension TagggedThing: Decodable { 
    private enum CodingKeys: String, CodingKey { 
     case tag 
    } 

    init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     let tag = try container.decode(String.self, forKey: .tag) 

     let singleValueContainer = try decoder.singleValueContainer() 
     switch tag { 
     case "foo": 
      // if it's not a Foo, throw and blame the server guy 
      self = .foo(try singleValueContainer.decode(Foo.self)) 
     case "bar": 
      self = .bar(try singleValueContainer.decode(Bar.self)) 
     case "baz": 
      self = .baz(try singleValueContainer.decode(Baz.self)) 
     default: 
      // this tag is unknown, or known but we don't care 
      self = .unknown 
     } 
    } 
} 

Teraz można dekodować następujące JSON:

let json: Data! = """ 
[ 
    {"tag": "foo", "fooValue": 1}, 
    {"tag": "bar", "barValue": 2}, 
    {"tag": "baz", "bazValue": 3} 
] 
""".data(using: .utf8) 

tak:

let taggedThings = try? JSONDecoder().decode([TagggedThing].self, from: json) 
+0

Dziękuję, to interesujące podejście. – zoul