如何在Swift中解析具有类型相关子对象的JSON对象?

时间:2021-12-28 07:32:52

I have the following JSON object:

我有以下JSON对象:

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

Depending on the type stored in type, the object in props has different keys. So, I tried with some logic

根据类型中存储的类型,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
        }
    }
}

but no luck

但没有运气

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)
                                      ^~~~~~~~

Then, I thought some class magic would probably do

然后,我认为一些阶级魔术可能会做

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
    }

    ...

but when I do dump the result of the parsing, I only get the default values

但是当我转储解析的结果时,我只获得默认值

▿ 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

My question is: what am I missing? Can this be done at all?

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

EDIT Following Kevin Ballard's suggestion, I get the following errors:

编辑按照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 个解决方案

#1


1  

Looking at your original listed errors, there are two distinct issues.

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

  1. You declared conformance to Codable, but the errors are telling you it cannot automatically synthesize Encodable. Your question isn't about encoding, but decoding, so for this I'd say just conform to Decodable instead of Codable (or implement encoding yourself).
  2. 您声明了对Codable的一致性,但错误告诉您它无法自动合成Encodable。你的问题不是关于编码,而是解码,所以对于这个我说只要符合Decodable而不是Codable(或自己实现编码)。
  3. props is of type PropTypes?, where PropTypes is an enum. You're decoding either FooProps or BarProps, and stuffing the result into props. You need to wrap the result in the enum instead. Also your enum is defined wrong, you have cases named FooProps and BarProps, which don't carry values. It should be redefined like { case foo(FooProps), bar(BarPros) } instead.
  4. props类型为PropTypes?,其中PropTypes是枚举。您正在解码FooProps或BarProps,并将结果填充到道具中。您需要将结果包装在枚举中。此外,您的枚举定义错误,您的案例名为FooProps和BarProps,它们不带值。它应该像{case foo(FooProps),bar(BarPros)}一样重新定义。

So together, this would look like

总而言之,这看起来像

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

#1


1  

Looking at your original listed errors, there are two distinct issues.

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

  1. You declared conformance to Codable, but the errors are telling you it cannot automatically synthesize Encodable. Your question isn't about encoding, but decoding, so for this I'd say just conform to Decodable instead of Codable (or implement encoding yourself).
  2. 您声明了对Codable的一致性,但错误告诉您它无法自动合成Encodable。你的问题不是关于编码,而是解码,所以对于这个我说只要符合Decodable而不是Codable(或自己实现编码)。
  3. props is of type PropTypes?, where PropTypes is an enum. You're decoding either FooProps or BarProps, and stuffing the result into props. You need to wrap the result in the enum instead. Also your enum is defined wrong, you have cases named FooProps and BarProps, which don't carry values. It should be redefined like { case foo(FooProps), bar(BarPros) } instead.
  4. props类型为PropTypes?,其中PropTypes是枚举。您正在解码FooProps或BarProps,并将结果填充到道具中。您需要将结果包装在枚举中。此外,您的枚举定义错误,您的案例名为FooProps和BarProps,它们不带值。它应该像{case foo(FooProps),bar(BarPros)}一样重新定义。

So together, this would look like

总而言之,这看起来像

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