Использование:

ользую Swift 4 и пытаюсь проанализировать некоторые данные JSON, которые, по-видимому, в некоторых случаях могут иметь разные значения типа для одного и того же ключа, например:

{
    "type": 0.0
}

а также

{
    "type": "12.44591406"
}

Я на самом деле застрял с определением моегоstruct потому что я не могу понять, как справиться с этим делом, потому что

struct ItemRaw: Codable {
    let parentType: String

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}

бросает"Expected to decode String but found a number instead."и, естественно,

struct ItemRaw: Codable {
    let parentType: Float

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}

бросает"Expected to decode Float but found a string/data instead." соответственно.

Как я могу справиться с этим (и аналогичными) случаями при определении моегоstruct?

Ответы на вопрос(3)

Одним из простых решений является обеспечение реализацииinit(from:) который пытается декодировать значение какStringи если это не удается из-за неправильного типа, попытайтесь декодировать какDouble:

public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    do {
        self.parentType = try container.decode(String.self, forKey: .parentType)
    } catch DecodingError.typeMismatch {
        let value = try container.decode(Double.self, forKey: .parentType)
        self.parentType = "\(value)"
    }
}
 errata16 окт. 2017 г., 12:20
Хотя этот ответ правильный (с небольшим исключением), он будет работать, только если мойstruct содержит одно свойство. Как только я начал добавлять дополнительные свойства, я столкнулся с ошибкойReturn from initializer without initializing all stored properties, Теперь я понимаю, что означает ошибка, и, возможно, я мог бы заявить, что в моем вопросе у меня будут другие свойства ... Я, тем не менее, буду голосовать за вас, но я приму другой ответ только потому, что он помещен во весь контекст и вознаградит вас новый пользователь с некоторой репутацией :) Большое спасибо за помощь!
 Ahmad F15 окт. 2017 г., 21:57
@vadian Я пробую этот код на своей детской площадке, я думаю, что нет необходимости вpublic func...
 vadian15 окт. 2017 г., 22:03
@AhmadF Я только что отменил (хуже) редактирование, не меньше и не больше.
 Ahmad F15 окт. 2017 г., 21:50
Обратите внимание, что это действительно при реализациикласс но не сСтруктурыЯ думаю, что вы должны удалитьrequired быть действительным для структур ...
 Itai Ferber16 окт. 2017 г., 15:43
@errata Э-э, хорошо. Если у вас есть дополнительные свойства, да, вам также нужно будет их декодировать и инициализировать, как и в любом другом инициализаторе в Swift.

Я должен был расшифроватьPHP/MySQL/PDO двойное значение, которое задается в виде строки, для этого варианта использования мне пришлось расширитьKeyedDecodingContainer, вот так:

extension KeyedDecodingContainer {
    func decode(forKey key: KeyedDecodingContainer.Key) throws -> Double {
        do {
            let str = try self.decode(String.self, forKey: key)
            if let dbl = Double(str) {
                return dbl
            }
        } catch DecodingError.typeMismatch {
            return try self.decode(Double.self, forKey: key)
        }
        let context = DecodingError.Context(codingPath: self.codingPath,
                                            debugDescription: "Wrong Money Value")
        throw DecodingError.typeMismatch(Double.self, context)
    }
}

Использование:

let data = """
{"value":"1.2"}
""".data(using: .utf8)!

struct Test: Decodable {
    let value: Double
    enum CodingKeys: String, CodingKey {
        case value
    }
    init(from decoder: Decoder) throws {
        self.value = try decoder.container(keyedBy: CodingKeys.self)
                                .decode(forKey: CodingKeys.value)
    }
}
try JSONDecoder().decode(Test.self, from: data).value
Решение Вопроса

когда пытался декодировать / кодировать «отредактированное» поле в ответе JSON Reddit Listing. Я создал структуру, которая представляет динамический тип, который может существовать для данного ключа. Ключ может иметь логическое или целое число.

{ "edited": false }
{ "edited": 123456 }

Если вам нужно только иметь возможность декодировать, просто используйте init (from :). Если вам нужно пойти в обе стороны, вам нужно будет реализовать функцию encode (to :).

struct Edited: Codable {
    let isEdited: Bool
    let editedTime: Int

    // Where we determine what type the value is
    init(from decoder: Decoder) throws {
        let container =  try decoder.singleValueContainer()

        // Check for a boolean
        do {
            isEdited = try container.decode(Bool.self)
            editedTime = 0
        } catch {
            // Check for an integer
            editedTime = try container.decode(Int.self)
            isEdited = true
        }
    }

    // We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try isEdited ? container.encode(editedTime) : container.encode(false)
    }
}

Внутри моего класса Codable я затем использую свою структуру.

struct Listing: Codable {
    let edited: Edited
}

Изменить: более конкретное решение для вашего сценария

Я рекомендую использовать протокол CodingKey и перечисление для хранения всех свойств при декодировании. Когда вы создаете что-то, что соответствует Codable, компилятор создаст для вас закрытое перечисление CodingKeys. Это позволяет вам решить, что делать, основываясь на ключе свойства JSON Object.

Например, это JSON, который я декодирую:

{"type": "1.234"}
{"type": 1.234}

Если вы хотите привести из String к Double, потому что вам нужно только значение double, просто декодируйте строку, а затем создайте из нее двойное значение. (Это то, что делает Итай Фербер, тогда вам придется также декодировать все свойства, используя try decoder.decode (type: forKey :))

struct JSONObjectCasted: Codable {
    let type: Double?

    init(from decoder: Decoder) throws {
        // Decode all fields and store them
        let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.

        // First check for a Double
        do {
            type = try container.decode(Double.self, forKey: .type)

        } catch {
            // The check for a String and then cast it, this will throw if decoding fails
            if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
                type = typeValue
            } else {
                // You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
                type = nil
            }
        }

        // Perform other decoding for other properties.
    }
}

Если вам нужно сохранить тип вместе со значением, вы можете использовать перечисление, соответствующее Codable, вместо структуры. Затем вы можете просто использовать оператор switch со свойством «type» JSONObjectCustomEnum и выполнять действия в зависимости от ситуации.

struct JSONObjectCustomEnum: Codable {
    let type: DynamicJSONProperty
}

// Where I can represent all the types that the JSON property can be. 
enum DynamicJSONProperty: Codable {
    case double(Double)
    case string(String)

    init(from decoder: Decoder) throws {
        let container =  try decoder.singleValueContainer()

        // Decode the double
        do {
            let doubleVal = try container.decode(Double.self)
            self = .double(doubleVal)
        } catch DecodingError.typeMismatch {
            // Decode the string
            let stringVal = try container.decode(String.self)
            self = .string(stringVal)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let value):
            try container.encode(value)
        case .string(let value):
            try container.encode(value)
        }
    }
}
 Leo Dabus16 окт. 2017 г., 05:53
Я думаю, что вы должны поймать любую ошибку также при декодировании editedTime. Это не выглядит хорошо, но я думаю, правильный синтаксисdo { isEdited = try container.decode(Bool.self) editedTime = 0 } catch { do { editedTime = try container.decode(Int.self) isEdited = true } catch { editedTime = 0 isEdited = false } }
 Latsu16 окт. 2017 г., 05:36
Вы действительно правы, спасибо за советы. Я обновлю фрагмент с вашим предложением.
 Leo Dabus16 окт. 2017 г., 01:52
try container.encode(isEdited ? editedTime : false)
 Leo Dabus16 окт. 2017 г., 05:21
это должно работатьtry isEdited ? container.encode(editedTime) : container.encode(false)
 Latsu16 окт. 2017 г., 05:09
@LeoDabus, было бы хорошо, если бы это выражение было допустимым. Компилятор Swift 4 не допускает несовпадения типов при использовании троичного оператора.

Ваш ответ на вопрос