/ / Swift 4 JSON Dekódovateľný najjednoduchší spôsob dekódovania zmeny typu - json, swift, swift4, zakódovateľný

Swift 4 JSON Dekódovateľný najjednoduchší spôsob dekódovania zmeny typu - json, swift, swift4, zakódovateľný

S rýchlym kódovaním protokolu je tu veľká úroveň pod hranicou dátumu a stratégiou konverzie údajov.

Vzhľadom na JSON:

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

Chcem ju prinútiť do nasledujúcej štruktúry

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

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

Stratégia na dekódovanie dátumu dokáže konvertovať dátum založený na reťazci na dátum.

Existuje niečo, čo robí to s floatom založeným na reťazcoch

V opačnom prípade som bol zaseknutý pomocou kódu CodingKey, aby priniesol reťazec a použil výpočtový systém:

    enum CodingKeys: String, CodingKey {
case name, age
case sTaxRate = "tax_rate"
}
var sTaxRate: String
var taxRate: Float { return Float(sTaxRate) ?? 0.0 }

Tento druh prameňov robí viac údržby, než sa zdá, by bolo potrebné.

Je to najjednoduchší spôsob, alebo existuje niečo podobné ako DateDecodingStrategy pre iné typy konverzií?

aktualizovať: Mala by som si všimnúť: "Takisto som prešiel cestou prevratu

init(from decoder:Decoder)

Ale to je v opačnom smere, pretože ma núti robiť to všetko pre seba.

odpovede:

13 pre odpoveď č. 1

Bohužiaľ neviem, že takáto možnosť existuje v súčasnosti JSONDecoder API. Existuje len možnosť, aby sa premeniť výnimočný hodnoty s pohyblivou rádovou čiarkou do a od reťazec reprezentácie.

Ďalším možným riešením ručného dekódovania je definovať a Codable typ balenia pre akékoľvek LosslessStringConvertible ktorý môže zakódovať a dekódovať z neho String reprezentácie:

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)
}
}

Potom môžete mať vlastnosť tohto typu a použiť auto-generované Codable zhody:

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"
}
}

Hoci, bohužiaľ, teraz musíte hovoriť v zmysle taxRate.decoded s cieľom komunikovať s používateľom Float hodnota.

Môžete však vždy definovať jednoduchú výpočtovú vlastnosť na presmerovanie, aby ste to zmiernili:

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"
}
}

Hoci to stále nie je ako klzký, ako by to malo byť - dúfajme, že neskoršia verzia JSONDecoder Rozhranie API bude obsahovať viac vlastných možností dekódovania alebo inak bude mať možnosť vyjadrovať typové konverzie v rámci Codable API sám.

Avšak jednou z výhod vytvorenia typu obalu je to, že sa dá použiť aj na zjednodušenie manuálneho dekódovania a kódovania. Napríklad pri ručnom dekódovaní:

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 pre odpoveď č. 2

Vždy môžete dekódovať ručne. Takže, vzhľadom na:

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

Môžete robiť:

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"
}
}

vidieť Kódovanie a dekódovanie ručne v Kódovanie a dekódovanie vlastných typov.

Ale súhlasím, že sa zdá, že by mal existovať elegantnejší proces premeny reťazcov, ktorý by bol ekvivalentný DateDecodingStrategy aké množstvo zdrojov JSON tam nesprávne vráti číselné hodnoty ako reťazce.


6 pre odpoveď č. 3

Podľa vašich potrieb si môžete vybrať jeden z nasledujúcich spôsobov, ako vyriešiť váš problém.


# 1. Použitím Decodable init(from:) inicializátor

Použite túto stratégiu, keď potrebujete konvertovať String na Float pre jednu štruktúru, enum alebo triedu.

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
}

}

využitie:

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. Použitie stredného modelu

Túto stratégiu použite, ak máte v JSONe veľa vnorených kľúčov, alebo ak potrebujete konvertovať veľa kľúčov (napr String na Float) z vašej 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
}

}

využitie:

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 pre odpoveď č. 4

Môžeš použiť lazy var na prevod vlastníctva na iný typ:

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

private var tax_rate: String
}

Jednou nevýhodou tohto prístupu je, že nemôžete definovať a let neustále, ak chcete získať prístup taxRate, pretože prvýkrát k nemu pristupujete, zmeníte struct.

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

1 pre odpoveď č. 5

Viem, že je to naozaj neskoro, ale začala som pracovať Codable len pár dní späť. A narazil som na podobnú otázku.

Ak chcete konvertovať reťazec na plávajúce číslo, môžete napísať rozšírenie na KeyedDecodingContainer a zavolajte metódu v rozšírení init(from decoder: Decoder){}

Pre problém uvedený v tejto otázke si pozrite príponu, ktorý som napísal nižšie.

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))
}
}

Túto metódu môžete zavolať init(from decoder: Decoder) metóda. Pozrite si príklad nižšie.

init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self)
}

V skutočnosti môžete tento prístup použiť na konverziu ľubovoľného typu údajov na akýkoľvek iný typ. Môžete konvertovať string to Date, string to bool, string to float, float to int atď.

V skutočnosti, aby som previedol reťazec na objekt Date, uprednostňujem tento prístup JSONEncoder().dateEncodingStrategy pretože ak to správne napíšete, môžete do rovnakej odpovede zahrnúť rôzne formáty dátumu.

Dúfam, že som pomohol.


-1 pre odpoveď č. 6

tu zadajte popis odkazuAko používať JSONDecodable v Swift4
1) získať odpoveď JSON a vytvoriť štruktúru 2) zodpovedajúce triedy Decodable v Struct 3) Ďalšie kroky v nasledujúcom projekte (jednoduchý príklad)