如何解码类型依赖于标记的值数组?

时间:2017-09-20 10:05:45

标签: json swift codable

我有一个带有数组值的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 }

有更好的方法吗?

3 个答案:

答案 0 :(得分:4)

您的数组包含有限,可枚举变种的异构对象;听起来像Swift枚举的完美用例。它不适合多态性,因为这些"事物"从概念上讲,它们不一定是同一种类。他们恰好被标记了。

以这种方式看待它:你有一系列都有标签的东西,有些属于这种类型,有些属于完全不同的类型,还有其他......有时你甚至不认识标签。 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
}

您的数组可以包含上述类型的任何实例,或者包含未知类型的实例。所以你有枚举TagggedThing(或更好的名字)。

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

您的数组,以Swift术语,类型为[TagggedThing]。因此,您将TagggedThing类型符合Decodable,如下所示:

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)

答案 1 :(得分:3)

可能是枚举可以使您的代码更清洁。每个案例都对应​​于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
}

答案 2 :(得分:1)

您还可以使用字典进行映射:

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]