Разбор JSON с подстановочными ключами - PullRequest
0 голосов
/ 01 марта 2019

Я анализирую плохо спроектированную структуру 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!

но это кажется очень сложным, что заставляет меня думать, что, может быть, есть более чистый способ сделать это?

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

Вы можете захватить идею «определенного, но в настоящее время неизвестного ключа» в структуре:

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))
}
0 голосов
/ 01 марта 2019

Вы можете использовать пользовательские ключи с 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)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...