使用通配符解析JSON

时间:2019-03-01 14:57:22

标签: json swift

我正在解析一个设计不良的JSON结构,在该结构中,我可以期望找到被重用作为指向进一步数据的键的值。像这样

 {"modificationDate" : "..."
  "type" : "...",
  "version" : 2,
  "manufacturer": "<WILDCARD-ID>"

  "<WILDCARD-ID>": { /* known structure */ } }

WILDCARD-ID在运行时几乎可以是任何东西,因此在编译时无法将其映射到结构中的某个字段。但是,一旦我取消引用该字段,它的值就具有已知的结构,这时我可以按照将JSON映射到struct的常规过程进行操作。

我发现自己走这条路

let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
let manDict = json["manufacturer"]
let data = NSKeyedArchiver.archivedData(withRootObject: manDict)
// now you have data!

但是这似乎非常circuit回,这使我认为也许有一种更干净的方法可以实现这一目标?

2 个答案:

答案 0 :(得分:2)

您可以通过Decodable使用自定义键,如下所示:

let json = """
    {
        "modificationDate" : "...",
        "type" : "...",
        "version" : 2,
        "manufacturer": "<WILDCARD-ID>",
        "<WILDCARD-ID>": {
            "foo": 1
        }
    }
    """.data(using: .utf8)!

struct InnerStruct: Decodable { // just as an example
    let foo: Int
}

struct Example: Decodable {
    let modificationDate: String
    let type: String
    let version: Int
    let manufacturer: String
    let innerData: [String: InnerStruct]

    enum CodingKeys: String, CodingKey {
        case modificationDate, type, version, manufacturer
    }

    struct CustomKey: CodingKey {
        var stringValue: String
        var intValue: Int?
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        init?(intValue: Int) {
            self.stringValue = "\(intValue)";
            self.intValue = intValue
        }
    }

    init(from decoder: Decoder) throws {
        // extract all known properties
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.modificationDate = try container.decode(String.self, forKey: .modificationDate)
        self.type = try container.decode(String.self, forKey: .type)
        self.version = try container.decode(Int.self, forKey: .version)
        self.manufacturer = try container.decode(String.self, forKey: .manufacturer)

        // get the inner structs with the unknown key(s)
        var inner = [String: InnerStruct]()
        let customContainer = try decoder.container(keyedBy: CustomKey.self)
        for key in customContainer.allKeys {
            if let innerValue = try? customContainer.decode(InnerStruct.self, forKey: key) {
                inner[key.stringValue] = innerValue
            }
        }

        self.innerData = inner
    }
}

do {
    let example = try JSONDecoder().decode(Example.self, from: json)
    print(example)
}

答案 1 :(得分:2)

您可以在结构中捕获“特定但当前未知的密钥”的概念:

struct StringKey: CodingKey {
    static let modificationDate = StringKey("modificationDate")
    static let type = StringKey("type")
    static let version = StringKey("version")
    static let manufacturer = StringKey("manufacturer")

    var stringValue: String
    var intValue: Int?
    init?(stringValue: String) { self.init(stringValue) }
    init?(intValue: Int) { return nil }
    init(_ stringValue: String) { self.stringValue = stringValue }
}

这样,解码就很简单,并且只对与键匹配的结构进行解码:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: StringKey.self)
    modificationDate = try container.decode(String.self, forKey: .modificationDate)
    type = try container.decode(String.self, forKey: .type)
    version = try container.decode(Int.self, forKey: .version)
    manufacturer = try container.decode(String.self, forKey: .manufacturer)

    // Decode the specific key that was identified by `manufacturer`,
    // and fail if it's missing
    manufacturerData = try container.decode(ManufacturerData.self,
                                            forKey: StringKey(manufacturer))
}