如何在Swift中解析具有依赖于类型的子对象的JSON对象?

时间:2017-09-26 21:24:08

标签: json swift swift4

我有以下JSON对象:

[{
    "type": "foo",
    "props": {
        "word": "hello"
    }
}, {
    "type": "bar",
    "props": {
        "number": 42
    }
}]

根据type中存储的类型,props中的对象具有不同的键。所以,我尝试了一些逻辑

struct MyObject : Codable {
    struct FooProps { let word: String }
    struct BarProps { var number: Int }
    enum PropTypes { case FooProps, BarProps }

    let type: String
    let props: PropTypes?

    enum CodingKeys : CodingKey {
        case type, props
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        type = try values.decode(String.self, forKey: .type)
        switch type {
        case "foo":
            props = try values.decode(FooProps.self, forKey: .props)
        case "bar":
            props = try values.decode(BarProps.self, forKey: .props)
        default:
            props = nil
        }
    }
}

但没有运气

error: jsontest.playground:10:8: error: type 'MyObject' does not conform to protocol 'Encodable'
struct MyObject : Codable {
       ^

jsontest.playground:16:9: note: cannot automatically synthesize 'Encodable' because 'MyObject.PropTypes?' does not conform to 'Encodable'
    let props: PropTypes?
        ^

error: jsontest.playground:27:39: error: cannot convert value of type 'MyObject.FooProps.Type' to expected argument type 'MyObject.PropTypes?.Type'
            props = try values.decode(FooProps.self, forKey: .props)
                                      ^~~~~~~~

error: jsontest.playground:29:39: error: cannot convert value of type 'MyObject.BarProps.Type' to expected argument type 'MyObject.PropTypes?.Type'
            props = try values.decode(BarProps.self, forKey: .props)
                                      ^~~~~~~~

然后,我认为一些魔法可能会做

class PropTypes : Codable { }
class FooProps : PropTypes { var word: String = "Default String" }
class BarProps : PropTypes { var number: Int = -1 }

class MyObject : Codable {
    let type: String
    var props: PropTypes?

    enum CodingKeys : CodingKey {
        case type, props
    }

    ...

但是当我dump解析结果时,我只得到默认值

▿ 2 elements
  ▿ __lldb_expr_32.MyObject #0
    - type: "foo"
    ▿ props: Optional(__lldb_expr_32.FooProps)
      ▿ some: __lldb_expr_32.FooProps #1
        - super: __lldb_expr_32.PropTypes
        - word: "Default String"
  ▿ __lldb_expr_32.MyObject #2
    - type: "bar"
    ▿ props: Optional(__lldb_expr_32.BarProps)
      ▿ some: __lldb_expr_32.BarProps #3
        - super: __lldb_expr_32.PropTypes
        - number: -1

我的问题是:我错过了什么?这可以完成吗?

编辑按照Kevin Ballard的建议,我收到以下错误:

error: jsontest.playground:15:37: error: no 'decode' candidates produce the expected contextual result type 'MyObject.FooProps'
            props = try .foo(values.decode(FooProps.self, forKey: .props))
                                    ^

jsontest.playground:15:37: note: overloads for 'decode' exist with these result types: Bool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Double, String, T
            props = try .foo(values.decode(FooProps.self, forKey: .props))
                                    ^

error: jsontest.playground:17:37: error: no 'decode' candidates produce the expected contextual result type 'MyObject.BarProps'
            props = try .bar(values.decode(BarProps.self, forKey: .props))
                                    ^

jsontest.playground:17:37: note: overloads for 'decode' exist with these result types: Bool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Float, Double, String, T
            props = try .bar(values.decode(BarProps.self, forKey: .props))
                                    ^

1 个答案:

答案 0 :(得分:1)

查看原始列出的错误,有两个不同的问题。

  1. 您声明符合Codable,但错误告诉您它无法自动合成Encodable。你的问题不是关于编码,而是关于解码,所以为此,我要说的是符合Decodable而不是Codable(或者自己实现编码)。
  2. props的类型为PropTypes?,其中PropTypes为枚举。您正在解码FooPropsBarProps,并将结果填入props。您需要将结果包装在枚举中。此外,您的枚举定义错误,您的案例名为FooPropsBarProps,它们不带值。它应该像{ case foo(FooProps), bar(BarPros) }一样重新定义。
  3. 所以在一起,这看起来像

    struct MyObject : Decodable {
        struct FooProps : Decodable { let word: String }
        struct BarProps : Decodable { var number: Int }
        enum PropTypes { case foo(FooProps), bar(BarProps) }
    
        let type: String
        let props: PropTypes?
    
        enum CodingKeys : CodingKey {
            case type, props
        }
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            type = try values.decode(String.self, forKey: .type)
            switch type {
            case "foo":
                props = try .foo(values.decode(FooProps.self, forKey: .props))
            case "bar":
                props = try .bar(values.decode(BarProps.self, forKey: .props))
            default:
                props = nil
            }
        }
    }