Swift Codable:用字典解析 JSON?多个不同的条目?

Swift Codable: parsing JSON with dictionary? Multiple different entries?

我有一个非常大的 Json 文件需要解析(几 MB),但没有合适的文档。生活艰难。据我所知,文件中的对象很容易识别。但是有些事情我不明白,简单的 Codable 协议失败了。在数组 layers 中,我可以找到三个 Layer 对象,每个对象都有 属性 elements。在 elements 我可以找到不止一个 elementData - 在第一层有 "ImageID":32dd...component,在第二层和第三层有 "name":..."contours": [...].可能会有更多的可能性。我的每次尝试都以错误结束。

抱歉这段代码会很长,但我不想删掉可能很重要的部分:

    "layers":[
    {
      "name":"img",
      "elements":[
        {
          "elementData":{
            "imageId":"32dd800000002"
          },
          "transform":{
            "xScale":100,
            "yScale":100
          },
          "active":true
        },
        {
          "component":{
            "glyphName":"e",
            "layerName":"img"
          }
        },
        {
          "elementData":{
            "composite":{
              "builder":{
                "builderGroup":{
                }
              }
            }
          },
          "transform":{
            "xOffset":120
          },
          "nonSpacing":true
        }
      ],
      "color":"maroon",
      "active":true
    },
    {
      "name":"Black",
      "elements":[
        {
          "component":{
            "glyphName":"e",
            "layerName":"Black"
          }
        },
        {
          "elementData":{
            "name":"caron",
            "contours":[
              {
                "nodes":[
                  "80 577",
                  "107 549  142 550  167 575 s"
                ]
              }
            ]
          }
        }
      ],
      "color":"#00802a"
    },
    {
      "name":"Thin",
      "elements":[
        {
          "component":{
            "glyphName":"e",
            "layerName":"Thin"
          }
        },
        {
          "elementData":{
            "name":"caron",
            "contours":[
              {
                "nodes":[
                  "102 597 s",
                  "118 580  132 580  148 597 s",
                  "250 710",
                  "235 726",
                  "110 613",
                  "140 613",
                  "14 726",
                  "-1 710"
                ]
              }
            ]
          }
        }
      ],
      "color":"#6a8000"
    }
  ],

如何处理?

https://app.quicktype.io , 正确 json

{ "layers":[
{
  "name":"img",
  "elements":[
    {
      "elementData":{
        "imageId":"32dd800000002"
      },
      "transform":{
        "xScale":100,
        "yScale":100
      },
      "active":true
    },
    {
      "component":{
        "glyphName":"e",
        "layerName":"img"
      }
    },
    {
      "elementData":{
        "composite":{
          "builder":{
            "builderGroup":{
            }
          }
        }
      },
      "transform":{
        "xOffset":120
      },
      "nonSpacing":true
    }
  ],
  "color":"maroon",
  "active":true
},
{
  "name":"Black",
  "elements":[
    {
      "component":{
        "glyphName":"e",
        "layerName":"Black"
      }
    },
    {
      "elementData":{
        "name":"caron",
        "contours":[
          {
            "nodes":[
              "80 577",
              "107 549  142 550  167 575 s"
            ]
          }
        ]
      }
    }
  ],
  "color":"#00802a"
},
{
  "name":"Thin",
  "elements":[
    {
      "component":{
        "glyphName":"e",
        "layerName":"Thin"
      }
    },
    {
      "elementData":{
        "name":"caron",
        "contours":[
          {
            "nodes":[
              "102 597 s",
              "118 580  132 580  148 597 s",
              "250 710",
              "235 726",
              "110 613",
              "140 613",
              "14 726",
              "-1 710"
            ]
          }
        ]
      }
    }
  ],
  "color":"#6a8000"
}
]}

解析

struct Welcome: Codable {
    let layers: [Layer]
}

struct Layer: Codable {
    let name: String
    let elements: [Element]
    let color: String
    let active: Bool?
}

struct Element: Codable {
    let elementData: ElementData?
    let transform: Transform?
    let active: Bool?
    let component: Component?
    let nonSpacing: Bool?
}

struct Component: Codable {
    let glyphName, layerName: String
}

struct ElementData: Codable {
    let imageID: String?
    let composite: Composite?
    let name: String?
    let contours: [Contour]?

    enum CodingKeys: String, CodingKey {
        case imageID = "imageId"
        case composite, name, contours
    }
}

struct Composite: Codable {
    let builder: Builder
}

struct Builder: Codable {
    let builderGroup: BuilderGroup
}

struct BuilderGroup: Codable {
}

struct Contour: Codable {
    let nodes: [String]
}

struct Transform: Codable {
    let xScale, yScale, xOffset: Int?
}

do {
     let res = try JSONDecoder().decode(Welcome.self,from:data)
 }
catch { 
    print(error)
}

编辑:

struct Welcome: Codable {
    let layers: [Layer]
}

struct Layer: Codable {
    let name: String
    let elements: [Element]
    let color: String
    let active: Bool?
}

struct Element: Codable {
    let elementData: ElementDataUnion?
    let transform: Transform?
    let active: Bool?
    let component: Component?
    let nonSpacing: Bool?
}

struct Component: Codable {
    let glyphName, layerName: String
}

enum ElementDataUnion: Codable {
    case elementDataClass(ElementDataClass)
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        if let x = try? container.decode(ElementDataClass.self) {
            self = .elementDataClass(x)
            return
        }
        throw DecodingError.typeMismatch(ElementDataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ElementDataUnion"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .elementDataClass(let x):
            try container.encode(x)
        case .string(let x):
            try container.encode(x)
        }
    }
}

struct ElementDataClass: Codable {
    let composite: Composite?
    let name: String?
    let contours: [Contour]?
}

struct Composite: Codable {
    let builder: Builder
}

struct Builder: Codable {
    let builderGroup: BuilderGroup
}

struct BuilderGroup: Codable {
}

struct Contour: Codable {
    let nodes: [String]
}

struct Transform: Codable {
    let xScale, yScale, xOffset: Int?
}

如果你需要 elementData 成为 dictionary/string , elements 成为 array/string 然后使用

struct Welcome: Codable {
    let layers: [Layer]
}

struct Layer: Codable {
    let name: String
    let elements: Elements
    let color: String
    let active: Bool?
}

enum Elements: Codable {
    case elementArray([Element])
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode([Element].self) {
            self = .elementArray(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Elements.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Elements"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .elementArray(let x):
            try container.encode(x)
        case .string(let x):
            try container.encode(x)
        }
    }
}

struct Element: Codable {
    let component: Component?
    let elementData: ElementDataUnion?
}

struct Component: Codable {
    let glyphName, layerName: String
}

enum ElementDataUnion: Codable {
    case elementDataClass(ElementDataClass)
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        if let x = try? container.decode(ElementDataClass.self) {
            self = .elementDataClass(x)
            return
        }
        throw DecodingError.typeMismatch(ElementDataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ElementDataUnion"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .elementDataClass(let x):
            try container.encode(x)
        case .string(let x):
            try container.encode(x)
        }
    }
}

struct ElementDataClass: Codable {
    let name: String
    let contours: [Contour]
}

struct Contour: Codable {
    let nodes: [String]
}