Расшифруйте JSON с пустым объектом, используя JSONDecoder - PullRequest
0 голосов
/ 04 февраля 2020

Я хочу декодировать JSON, используя JSONDecoder. Он работает, как и ожидалось, но для JSON, где внутренний объект пуст JSONDecoder выдает ошибку The data couldn’t be read because it is missing.

Sample JSON при ошибке:

{
    "header": {
        "code": 1053,
        "message": "Incorrect information."
    },
    "body": {}
}

Sample JSON в случае успеха:

{
    "header": {
        "code": 1053
        "message": "Nice information."
    },
    "body": {
        "client_id": 12345
    }
}

в случае успеха JSON, он легко декодируется. Но при ошибке JSON возникает ошибка.

Вот код, который я использую

struct ApiResponse: Decodable {

    let header: Header
    let body: Body

    struct Header: Decodable {
        let responseCode: Int
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int
    }
}

let decoder: JSONDecoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode(ApiResponse.self, from: data)

Ответы [ 3 ]

2 голосов
/ 04 февраля 2020

Вы можете расширить KeyedDecodingContainer для обработки пустого словаря как nil с протоколом

public protocol EmptyDictionaryRepresentable {
    associatedtype CodingKeys : RawRepresentable where CodingKeys.RawValue == String
    associatedtype CodingKeyType: CodingKey = Self.CodingKeys
}

extension KeyedDecodingContainer {
    public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T?
        where T : Decodable & EmptyDictionaryRepresentable
    {
        guard contains(key) else { return nil }
        let container = try nestedContainer(keyedBy: type.CodingKeyType.self, forKey: key)
        return container.allKeys.isEmpty ? nil : try decode(T.self, forKey: key)
    }
}

Чтобы использовать его, примите протокол и объявите уязвимое свойство как необязательное

struct ApiResponse: Decodable {
    let header: Header
    let body: Body?
}

struct Body: Decodable, EmptyDictionaryRepresentable {
    enum CodingKeys : String, CodingKey { case clientId = "client_id" }

    let clientId: Int
}

Предупреждение: это решение не работает со стратегией .convertFromSnakeCase

Примечание. Обратите внимание на несоответствие имени элемента struct key в Header

1 голос
/ 04 февраля 2020

Вы всегда можете реализовать свою собственную функцию декодирования, которая позволяет вам делать все, что вы хотите, но быстрый способ обойти это просто пометить все, что не гарантированно будет возвращаться как необязательное.

Имейте в виду, если ключ возвращается сервером, затем он должен правильно декодироваться. Обычно я бы предложил сделать Body необязательным ... однако здесь это не сработает.

В этом случае вы захотите сделать:

struct ApiResponse: Decodable {

    let header: Header
    let body: Body // key is being returned so we keep it as part of the response

    struct Header: Decodable {
        let code: Int // match your variables with the JSON being returned
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int? // sometimes nothing comes inside the body dict, so make its internals optional
    }
}

РЕДАКТИРОВАТЬ:

ТАКЖЕ, как отметил Лео в комментариях, вы также совершили глупую ошибку, фактически не сопоставив ваши переменные с ответом. Обратите внимание, что ваш JSON имеет code в качестве ключа, в то время как ваш Header объект ищет responseCode Я отредактировал свой первоначальный ответ, чтобы также внести это изменение.

0 голосов
/ 04 февраля 2020

Объявите clientId как необязательное свойство. Потому что в вашей ошибке JSON client_id не существует. Для получения дополнительной информации прочитайте Статья .

...