Как я могу декодировать поля данных JSON, которые имеют изменяемые типы? - PullRequest
0 голосов
/ 15 сентября 2018

Я получаю данные JSON от API, но есть некоторые поля, которые иногда являются строками, а другие - целыми числами. Каковы лучшие решения для чего-то подобного?

Вот мой код декодирования:

public struct Nutriments {
    public let energy: String?
    public let energyServing: String?
    public let energy100g: String?

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        energy = try container.decodeIfPresent(String.self, forKey: .energy)
        energy100g = try container.decodeIfPresent(String.self, forKey: .energy100g)
        energyServing = try container.decodeIfPresent(String.self, forKey: .energyServing)
        }
}

Пример JSON:

"nutriments": {
        "energy_100g": 8.97,
        "energy_serving": "55",
        "energy": "7"
}

И другие времена, подобные этому:

"nutriments": {
        "energy_100g": "8.97",
        "energy_serving": 55,
        "energy": 7
}

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

Решением было бы объявить пользовательскую оболочку для Double, которая знает, как декодировать себя из строк или двойных чисел:

struct DoubleLike: Decodable {
    public let value: Double

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            value = try container.decode(Double.self)
        } catch DecodingError.typeMismatch {
            let valueString = try container.decode(String.self)
            if let dbl = Double(valueString) {
                value = dbl
            } else {
                throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Could not convert \(valueString) to Double"))
            }
        }
    }
}

Вы можете легко использовать это в своей структуре:

struct Nutriments: Decodable {
    public let energy: DoubleLike
    public let energyServing: DoubleLike
    public let energy100g: DoubleLike

    enum CodingKeys: String, CodingKey {
        case energy
        case energyServing = "energy_serving"
        case energy100g = "energy_100g"
    }
}

Преимущество этого решения в том, что оно масштабируемое, недостатком является то, что вам всегда нужно извлекать свойство .value, прежде чем вы сможете использовать декодированный double.

Другоерешение будет включать в себя написание собственной Decoder реализации, однако это может не стоить усилий.

0 голосов
/ 15 сентября 2018

Прежде всего обвините владельца службы в отправке противоречивых данных.

Чтобы декодировать оба типа, вы можете проверить тип в методе init.

По крайней мере, API кажетсяотправить все ключи, чтобы вы могли объявить все члены структуры как необязательные

public struct Nutriments {

    public let energy: String
    public let energyServing: String
    public let energy100g: String

    public init(from decoder: Decoder) throws {      
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do { energy = try container.decode(String.self, forKey: .energy) }
        } catch DecodingError.typeMismatch { energy = String(try container.decode(Int.self, forKey: .energy)) }
        do { energy100g = try container.decode(String.self, forKey: .energy100g) }
        } catch DecodingError.typeMismatch { energy100g = String(try container.decode(Int.self, forKey: .energy100g)) }
        do { energyServing = try container.decode(String.self, forKey: .energyServing) }
        } catch DecodingError.typeMismatch { energyServing = String(try container.decode(Int.self, forKey: .energyServing)) }
    }
}
...