Декодируемый и JSON, 2 типа данных для одной и той же переменной

Я использую протокол Decodable для декодирования некоторых json, но у меня возникла проблема:

Я получаю ответ, где долгота и широта могут быть либо целым числом (широта = 0), если к элементу не добавлены данные о геолокации, либо строкой (fx. Latitude = "25.047880"), если есть геоданные. доступный. Теперь, когда я декодирую json, я не знаю, как построить свой Struct, так как long и lat не могут быть одновременно String и Int .. Итак, я получаю ошибку декодирования при выборке элементов, где представлены оба случая.

Есть предложения о том, как это решить? Я пробовал использовать "Any" в качестве типа данных, но это не соответствует протоколу Decodable.

struct JPhoto: Decodable {
  let id: String
  let farm: Int
  let secret: String
  let server: String
  let owner: String
  let title: String
  let latitude: String //Can both be Int and String
  let longitude: String //Can both be Int and String
}

person Nicolai Harbo    schedule 08.04.2018    source источник
comment
Вы должны написать собственный инициализатор для обработки случаев. Прочтите Пользовательские типы кодирования и декодирования   -  person vadian    schedule 08.04.2018


Ответы (1)


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

enum AngularDistance:Codable {
    case string(String), integer(Int)

    func encode(to encoder: Encoder) throws {
        switch self {
        case .string(let str):
            var container = encoder.singleValueContainer()
            try container.encode(str)
        case .integer(let int):
            var container = encoder.singleValueContainer()
            try container.encode(int)
        }
    }

    init(from decoder: Decoder) throws {
        do {
            let container = try decoder.singleValueContainer()
            let str = try container.decode(String.self)
            self = AngularDistance.string(str)
        }
        catch {
              do { let container = try decoder.singleValueContainer()
                   let int = try container.decode(Int.self)
                   self = AngularDistance.integer(int) 
              }
              catch {
                   throw DecodingError.typeMismatch(AngularDistance.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected to decode an Int or a String"))
              }
        }
    }
}

Вот пример кодирования и декодирования этого AngularDistance типа:

let lat = [AngularDistance.string("String"), AngularDistance.integer(10)]
let encoder = JSONEncoder()
var decoder = JSONDecoder()

do {
    let encoded = try encoder.encode(lat)
    try decoder.decode(Array<AngularDistance>.self, from: encoded)
}
catch DecodingError.typeMismatch(let t, let e)  {
    t
    e.codingPath
    e.debugDescription
}
catch {
    print(error.localizedDescription)
    }

И вот ваша структура переписана:

struct JPhoto: Decodable {
  let id: String
  let farm: Int
  let secret: String
  let server: String
  let owner: String
  let title: String
  let latitude: AngularDistance //Can both be Int and String
  let longitude: AngularDistance //Can both be Int and String
}
person sketchyTech    schedule 08.04.2018
comment
Спасибо за ваш комментарий! В этом есть смысл, по большей части! Что меня немного смущает, так это то, нужен ли мне средний сниппет кода? Мол, мне просто нужно добавить перечисление, а затем переписать структуру, верно? :-) - person Nicolai Harbo; 08.04.2018
comment
Кстати, вы не обрабатывали случаи ошибок. Что, если возвращенное значение не String или Int? Вы должны обработать эти сценарии, явно выбрасывая DecodingError. - person nayem; 08.04.2018
comment
@nayem Как и в случае со всеми другими свойствами в структуре Codable, если значение не существует или если это другой тип, то при попытке декодирования экземпляром JSONDecoder будет выдана ошибка. Codable просто не будет анализировать неправильно отформатированную строку и выдает ошибку. Данные не могут быть прочитаны, потому что они имеют неправильный формат. - person sketchyTech; 08.04.2018
comment
@NicolaiHarbo да, вы просто переписываете структуру, но при обработке полученных значений нужно знать, что вам нужно извлечь связанные значения. - person sketchyTech; 08.04.2018
comment
Хотя это правда то, что вы сказали. Но это ведь не пояснительное описание? Это довольно расплывчато. - person nayem; 08.04.2018
comment
Вы можете добавить обработку ошибок, если хотите, но я бы не считал это важным, если вы не собираетесь тестировать каждый тип одинаковым образом. Итак, да, если вы добавляете обработку ошибок в String, Int, Bool, Dictionary и Array, это может быть полезно для отладки. Но просто выделить один тип и сказать, что мы должны добавить обработку ошибок, само по себе не принесет особой пользы. - person sketchyTech; 08.04.2018
comment
Почему нет? Хорошо, давайте попробуем расшифровать Bool и расскажем, какую ошибку вы получаете? Затем я покажу вам, как это исправить. - person nayem; 08.04.2018
comment
@nayem Хорошо, есть перечисление DecodingError, о котором я не знал. Спасибо, что побудили меня исследовать это. На основе этого я добавил в свой пример элементарную поддержку ошибок. - person sketchyTech; 08.04.2018
comment
Да! Вот и все. Я указывал вам на это. Рад, что ты со всем разобрался. - person nayem; 09.04.2018