2017-09-20 6 views
6

私は値の配列とJSONを持っている:タイプがタグに依存する値の配列をデコードするには?

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

私は、特定のタイプは、タグによって異なりstructの配列にこの配列を解読したい:

私はどのように
protocol SomeCommonType {} 

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

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

それを行う?その後、

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 … 
     } 
    } 
} 

そして:現時点では私はこの少し醜いラッパーを持って

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

は、より良い方法はありますか?

+0

私はあなたがいないだと仮定していますそのjsonを送る人。私が考えることのできる唯一の他の方法は、文字列から辞書を辞書に格納しておき、タグが辞書内にあるかどうかを確認し、対応する型を取得してそこからオブジェクトを初期化することです。 (私はいくつか細かい詳細を除外しましたが、あなたは主なアイデアを得ると思います)。 – TNguyen

+1

https:// stackoverflowの2番目の部分を比較してください。もう一つのアプローチのためのcom/a/44473156/2976878(あなたがそれをより良く考えているかどうかわからない、まだラッパータイプを使用しています) – Hamish

+0

@Hamish、その質問は私が後にしているものなので、賞金の期限が切れた後に詐欺師として。ありがとうございました! – zoul

答えて

2

かもしれ列挙型は、あなたのコードビットクリーンにすることができます。それぞれのケースはjsonのタイプ(タグ)に対応します。場合によっては、適切なモデルにjsonを解析します。とにかくモデルを選ぶべきなんらかの評価が必要です。だから私はあなたにもマッピングに辞書を使用することができ、この

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

'decoder'シグネチャの中の' JSON'型は何ですか?私はSwiftの「Codable」を使ってJSONをデコードしようとしています。これは、JSONをデコードするために 'Codable'が型を必要とするためです(これはcatch 22です)。 – zoul

0

に来ました:

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

「Codable」を使用してJSONからタグをどのように読み込みますか? – zoul

3

あなたの配列は有限、可算品種の異種のオブジェクトが含まれています。 Swift enumの完璧な使用例のように聞こえます。これらの「物」は必ずしも同じ種類のものではなく、概念的に言えば、多形性には適していません。彼らはちょうどタグが付けられます。これで

見てこの方法:あなたはすべてのタグを持っているものの配列を持っている、いくつかは...他の人はまだ他の完全に異なる種類のものであり、そして、この種のものであり、時にはあなたも認識していませんタグ。 Swift enumはこのアイデアを捉えるのに最適な手段です。

ですから、タグプロパティを共有するが、互いにそれ以外の場合は完全に異なる構造体の束があります

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 
} 

をそして、あなたの配列は、上記のタイプの、または未知のタイプの任意のインスタンスを含めることができます。だからあなたはenum TagggedThing(またはより良い名前)を持っています。

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

あなたの配列は、スウィフトでは[TagggedThing]です。ですから、このようなDecodableTagggedThingタイプに準拠:

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 
     } 
    } 
} 

今、あなたは、次のJSONをデコードすることができます。このような

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

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

ありがとう、これは面白いアプローチです。 – zoul

関連する問題