Swift 4 декодируется с неизвестными динамическими клавишами - PullRequest
0 голосов
/ 12 ноября 2018

У меня есть следующий JSON

{"DynamicKey":6410,"Meta":{"name":"","page":""}}

DynamicKey неизвестен во время компиляции. Я пытаюсь найти ссылку, как разобрать эту структуру, используя decodable.

public struct MyStruct: Decodable {
    public let unknown: Double
    public let meta: [String: String]

    private enum CodingKeys: String, CodingKey {
        case meta = "Meta"
    }
}

Есть идеи?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018
import UIKit

var str = """
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
"""
public struct MyStruct: Decodable {
    public var unknown: Double?
    public var meta: [String: String]?

    public init(from decoder: Decoder) {

        guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
            fatalError()
        }
            for key in container.allKeys {
                unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
                if key.stringValue == "Meta" {
                    meta = try? container.decode([String: String].self, forKey: key)
                }

            }
            print(container.allKeys)
    }

    struct CodingKeys: CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }
}
    let jsonData = str.data(using: .utf8)!
    let jsonDecoder = JSONDecoder()
    let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
    print("Meta : \(myStruct.meta)")
    print("Double : \(myStruct.unknown)")

Я уже ответил на аналогичный вопрос

https://stackoverflow.com/a/48412139/1979882

0 голосов
/ 12 ноября 2018

Чтобы декодировать произвольную строку, вам нужен ключ, подобный этому:

// Arbitrary key
private struct Key: CodingKey, Hashable, CustomStringConvertible {
    static let meta = Key(stringValue: "Meta")!

    var description: String {
        return stringValue
    }

    var hashValue: Int { return stringValue.hash }

    static func ==(lhs: Key, rhs: Key) -> Bool {
        return lhs.stringValue == rhs.stringValue
    }

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

Это инструмент общего назначения (за исключением static let meta), который можно использовать для решения любых проблем с общими ключами.

С этим вы можете найти первый ключ, который не является .meta, и использовать его в качестве динамического ключа.

public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: Key.self)

    meta = try container.decode([String: String].self, forKey: .meta)

    guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
                                                                debugDescription: "Could not find dynamic key"))
    }

    unknown = try container.decode(Double.self, forKey: dynamicKey)
}

Все вместе как игровая площадка:

import Foundation

let json = Data("""
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
""".utf8)

public struct MyStruct: Decodable {
    public let unknown: Double
    public let meta: [String: String]

    // Arbitrary key
    private struct Key: CodingKey, Hashable, CustomStringConvertible {
        static let meta = Key(stringValue: "Meta")!
        var description: String {
            return stringValue
        }

        var hashValue: Int { return stringValue.hash }

        static func ==(lhs: Key, rhs: Key) -> Bool {
            return lhs.stringValue == rhs.stringValue
        }

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

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        meta = try container.decode([String: String].self, forKey: .meta)

        guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
            throw DecodingError.dataCorrupted(.init(codingPath: [],
                                                    debugDescription: "Could not find dynamic key"))
        }

        unknown = try container.decode(Double.self, forKey: dynamicKey)
    }
}


let myStruct = try! JSONDecoder().decode(MyStruct.self, from: json)
myStruct.unknown
myStruct.meta

Эта техника может быть расширена до декодировать произвольный JSON . Иногда это проще сделать, а затем вытащить нужные фрагменты, а затем декодировать каждый фрагмент. Например, с помощью приведенной выше сущности JSON вы можете реализовать MyStruct следующим образом:

public struct MyStruct: Decodable {
    public let unknown: Double
    public let meta: [String: String]

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let json = try container.decode(JSON.self)

        guard let meta = json["Meta"]?.dictionaryValue as? [String: String] else {
            throw DecodingError.dataCorrupted(.init(codingPath: [],
                                                    debugDescription: "Could not find meta key"))
        }
        self.meta = meta

        guard let (_, unknownJSON) = json.objectValue?.first(where: { (key, _) in key != "Meta" }),
            let unknown = unknownJSON.doubleValue
        else {
            throw DecodingError.dataCorrupted(.init(codingPath: [],
                                                    debugDescription: "Could not find dynamic key"))
        }
        self.unknown = unknown
    }
}
...