Swift 4 JSON Декодируемый простейший способ декодирования изменения типа
С протоколом Swift4 Codable существует большой уровень под капотом и страtagsи преобразования данных.
Учитывая JSON:
{ "name": "Bob", "age": 25, "tax_rate": "4.25" }
Я хочу принудить его к следующей структуре
- UIApplication.registerForRemoteNotifications () необходимо вызывать только из основного streamа
- coca pod Диаграмма не появляется (Swift4)
- Как декодировать вложенную структуру JSON с протоколом Swift Decodable?
- Массивы декодирования Swift JSONDecode терпят неудачу, если сбой одного элемента
- Как использовать swift 4 Codable в базовых данных?
struct ExampleJson: Decodable { var name: String var age: Int var taxRate: Float enum CodingKeys: String, CodingKey { case name, age case taxRate = "tax_rate" } }
Страtagsя декодирования даты может преобразовать дату, основанную на String, в Date.
Есть ли что-то, что делает это с помощью String based Float
В противном случае я застрял в использовании CodingKey для приведения строки и использования вычислений:
enum CodingKeys: String, CodingKey { case name, age case sTaxRate = "tax_rate" } var sTaxRate: String var taxRate: Float { return Float(sTaxRate) ?? 0.0 }
Этот вид нитей, которые я делаю больше обслуживания, чем кажется, должен быть необходим.
Это самый простой способ или есть нечто похожее на DateDecodingStrategy для других преобразований типов?
Обновление : я должен отметить: я также пошел по пути переопределения
init(from decoder:Decoder)
Но это в противоположном направлении, поскольку это заставляет меня делать все это для себя.
- Использование вывода Swift 3 @objc в режиме Swift 4 устарело?
- Как преобразовать строку даты с необязательными дробными секундами, используя Codable в Swift4
- Кодирование / декодирование массивов типов, соответствующих протоколу с JSONEncoder
- Как декодировать свойство с типом словаря JSON в Swift 4 decodable protocol
- Штрих-код на скорости 4
- Swift 4 "Этот class не соответствует кодированию ключевого значения"
- Как я могу использовать индексы строковой развертки в Swift 4?
- Как использовать пользовательские ключи с протоколом Swift 4 Decodable?
К сожалению, я не считаю, что такой вариант существует в текущем API JSONDecoder
. Существует только опция для преобразования исключительных значений с плавающей запятой в строковое представление и из него.
Другим возможным решением для декодирования вручную является определение типа оболочки Codable
для любого LosslessStringConvertible
который может кодировать и декодировать из представления String
:
struct StringCodableMap : Codable { var decoded: Decoded init(_ decoded: Decoded) { self.decoded = decoded } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let decodedString = try container.decode(String.self) guard let decoded = Decoded(decodedString) else { throw DecodingError.dataCorruptedError( in: container, debugDescription: """ The string \(decodedString) is not representable as a \(Decoded.self) """ ) } self.decoded = decoded } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(decoded.description) } }
Тогда вы можете просто иметь свойство этого типа и использовать автоматически сгенерированное соответствие Codable
:
struct Example : Codable { var name: String var age: Int var taxRate: StringCodableMap private enum CodingKeys: String, CodingKey { case name, age case taxRate = "tax_rate" } }
Хотя, к сожалению, теперь вам приходится говорить с точки зрения taxRate.decoded
, чтобы взаимодействовать с значением Float
.
Однако вы всегда можете определить простое вычисляемое свойство пересылки, чтобы облегчить это:
struct Example : Codable { var name: String var age: Int private var _taxRate: StringCodableMap var taxRate: Float { get { return _taxRate.decoded } set { _taxRate.decoded = newValue } } private enum CodingKeys: String, CodingKey { case name, age case _taxRate = "tax_rate" } }
Хотя это все еще не так гладко, как должно быть на самом деле – надеюсь, что более поздняя версия API JSONDecoder
будет включать в себя более настраиваемые параметры декодирования, а также возможность выражать преобразования типов внутри самого Codable
API.
Однако одним из преимуществ создания типа оболочки является то, что он также может использоваться для упрощения ручного декодирования и кодирования. Например, при ручном расшифровке:
struct Example : Decodable { var name: String var age: Int var taxRate: Float private enum CodingKeys: String, CodingKey { case name, age case taxRate = "tax_rate" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) self.age = try container.decode(Int.self, forKey: .age) self.taxRate = try container.decode(StringCodableMap.self, forKey: .taxRate).decoded } }
Вы всегда можете декодировать вручную. Итак, учитывая:
{ "name": "Bob", "age": 25, "tax_rate": "4.25" }
Ты можешь сделать:
struct Example: Codable { let name: String let age: Int let taxRate: Float init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) name = try values.decode(String.self, forKey: .name) age = try values.decode(Int.self, forKey: .age) guard let rate = try Float(values.decode(String.self, forKey: .taxRate)) else { throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.taxRate], debugDescription: "Expecting string representation of Float")) } taxRate = rate } enum CodingKeys: String, CodingKey { case name, age case taxRate = "tax_rate" } }
См. « Кодирование и декодирование вручную при кодировании и декодировании пользовательских типов» .
Но я согласен, что кажется, что должен быть более элегантный процесс преобразования строк, эквивалентный DateDecodingStrategy
учитывая, сколько источников JSON там неверно возвращают числовые значения в виде строк.
В соответствии с вашими потребностями вы можете выбрать один из двух способов, чтобы решить вашу проблему.
# 1. Использование Decodable
init(from:)
initializer
Используйте эту страtagsю, когда вам нужно преобразовать из String
в Float
для одной структуры, enums или classа.
import Foundation struct ExampleJson: Decodable { var name: String var age: Int var taxRate: Float enum CodingKeys: String, CodingKey { case name, age, taxRate = "tax_rate" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: CodingKeys.name) age = try container.decode(Int.self, forKey: CodingKeys.age) let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate) guard let taxRateFloat = Float(taxRateString) else { let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object") throw DecodingError.dataCorrupted(context) } taxRate = taxRateFloat } }
Применение:
import Foundation let jsonString = """ { "name": "Bob", "age": 25, "tax_rate": "4.25" } """ let data = jsonString.data(using: String.Encoding.utf8)! let decoder = JSONDecoder() let exampleJson = try! decoder.decode(ExampleJson.self, from: data) dump(exampleJson) /* prints: ▿ __lldb_expr_126.ExampleJson - name: "Bob" - age: 25 - taxRate: 4.25 */
# 2. Использование промежуточной модели
Используйте эту страtagsю, когда у вас много вложенных ключей в JSON или когда вам нужно преобразовать многие ключи (например, от String
to Float
) из вашего JSON.
import Foundation fileprivate struct PrivateExampleJson: Decodable { var name: String var age: Int var taxRate: String enum CodingKeys: String, CodingKey { case name, age, taxRate = "tax_rate" } } struct ExampleJson: Decodable { var name: String var age: Int var taxRate: Float init(from decoder: Decoder) throws { let privateExampleJson = try PrivateExampleJson(from: decoder) name = privateExampleJson.name age = privateExampleJson.age guard let convertedTaxRate = Float(privateExampleJson.taxRate) else { let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object") throw DecodingError.dataCorrupted(context) } taxRate = convertedTaxRate } }
Применение:
import Foundation let jsonString = """ { "name": "Bob", "age": 25, "tax_rate": "4.25" } """ let data = jsonString.data(using: String.Encoding.utf8)! let decoder = JSONDecoder() let exampleJson = try! decoder.decode(ExampleJson.self, from: data) dump(exampleJson) /* prints: ▿ __lldb_expr_126.ExampleJson - name: "Bob" - age: 25 - taxRate: 4.25 */
Вы можете использовать lazy var
для преобразования свойства в другой тип:
struct ExampleJson: Decodable { var name: String var age: Int lazy var taxRate: Float = { Float(self.tax_rate)! }() private var tax_rate: String }
Одним из недостатков этого подхода является то, что вы не можете определить константу let
если вы хотите получить доступ к taxRate
, так как при первом доступе к ней вы мутируете структуру.
// Cannot use `let` here var example = try! JSONDecoder().decode(ExampleJson.self, from: data)
Я знаю, что это очень поздний ответ, но я начал работать только на Codable
пару дней назад. И я столкнулся с подобной проблемой.
Чтобы преобразовать строку в число с плавающей запятой, вы можете написать расширение для KeyedDecodingContainer
и вызвать метод в расширении из init(from decoder: Decoder){}
Для проблемы, упомянутой в этом выпуске, см. Расширение, которое я написал ниже;
extension KeyedDecodingContainer { func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? { guard let value = try decodeIfPresent(transformFrom, forKey: key) else { return nil } return Float(value) } func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? { return Float(try decode(transformFrom, forKey: key)) } }
Вы можете вызвать этот метод из метода init(from decoder: Decoder)
. См. Пример ниже;
init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self) }
Фактически, вы можете использовать этот подход для преобразования любого типа данных в любой другой тип. Вы можете преобразовать string to Date
, string to bool
, string to float
, float to int
и т. Д.
На самом деле, чтобы преобразовать строку в объект Date, я предпочел бы этот подход по сравнению с JSONEncoder().dateEncodingStrategy
потому что, если вы его правильно напишете, вы можете включить разные форматы дат в один и тот же ответ.
Надеюсь, я помог.
введите ссылку здесь здесь. Как использовать JSONDecodable в Swift4
1) получить JSON Response и Create Struct 2) соответствовать classу Decodable в Struct 3) Другие шаги в следующем проекте (простой пример)