/ / Swift 4 JSON Декодируемый найпростіший спосіб декодування зміни типу - json, swift, swift4, кодований

Швидкий 4 JSON декодувальний найпростіший спосіб декодування зміни типу - json, swift, swift4, кодований

З протоколом кодування SWIFTOS4 є чудовий рівень під час дати капота та стратегії перетворення даних.

Враховуючи JSON:

{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}

Я хочу примусити його до наступної структури

struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float

enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}

Стратегія декодування дат може перетворити дату на основі рядка в дату.

Чи є щось таке, що робить це за допомогою 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)

Але це в протилежному напрямку, оскільки це змушує мене робити це все для себе.

Відповіді:

13 за відповідь № 1

На жаль, я не вірю, що такий варіант існує в поточному JSONDecoder API Там існує лише один варіант для того, щоб конвертувати винятковий значення з плаваючою комою від імені рядка.

Іншим можливим рішенням для декодування вручну є визначення a Codable обгортковий тип для будь-якого LosslessStringConvertible що може кодувати і декодувати з нього String представлення:

struct StringCodableMap<Decoded : LosslessStringConvertible> : 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<Float>

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<Float>

var taxRate: Float {
get { return _taxRate.decoded }
set { _taxRate.decoded = newValue }
}

private enum CodingKeys: String, CodingKey {
case name, age
case _taxRate = "tax_rate"
}
}

Хоча це все ще не таке плямисте, як воно має бути - сподіваюся, більш пізня версія JSONDecoder API включатиме більше варіантів користувацького декодування, або мати можливість виразити перетворення типу в 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<Float>.self,
forKey: .taxRate).decoded
}
}

10 за відповідь № 2

Ви завжди можете декодувати вручну. Отже, дано:

{
"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 там неправильно повертають числові значення як рядки.


6 за відповідь № 3

Відповідно до ваших потреб, ви можете обрати один із двох способів, щоб вирішити вашу проблему.


№1 Використовуючи Decodable init(from:) ініціалізатор

Використовуйте цю стратегію, коли вам потрібно перетворити з String до Float для одного структуру, переліку або класу.

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 Використання проміжної моделі

Використовуйте цю стратегію, коли у вас є багато вкладених ключів у вашому JSON або коли вам потрібно перетворити багато ключів (наприклад, з String до 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
*/

1 для відповіді № 4

Ви можете використовувати lazy var перетворити властивість на інший тип:

struct ExampleJson: Decodable {
var name: String
var age: Int
lazy var taxRate: Float = {
Float(self.tax_rate)!
}()

private var tax_rate: String
}

Один недолік цього підходу полягає в тому, що ви не можете визначити a let постійне, якщо ви хочете отримати доступ taxRate, оскільки вперше ви отримуєте доступ до нього, ви мутуєте структуру.

// Cannot use `let` here
var example = try! JSONDecoder().decode(ExampleJson.self, from: data)

1 для відповіді № 5

Я знаю, що це дуже пізня відповідь, але я почав працювати над цим 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 тому що якщо ви напишете це належним чином, ви можете включити різні формати дат в одній і тій же відповідь.

Надія я допомогла.


-1 для відповіді № 6

введіть опис посилання тутЯк використовувати JSONDecodable у Swift4
1) отримати JSON Response і Create Структуру 2) відповідають класу Decodable в Struct 3) Інші кроки у виконанні проекту (простий приклад)