/ / Swift 4 JSON Dekodowalny najprostszy sposób dekodowania zmiany typu - json, swift, swift4, codable

Swift 4 JSON Dekodowalny najprostszy sposób dekodowania zmiany typu - json, swift, swift4, codable

Dzięki szyfrowanemu protokołowi swift4 jest świetny poziom strategii i strategii konwersji danych.

Biorąc pod uwagę JSON:

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

Chcę przymusić go do następującej struktury

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

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

Strategia dekodowania daty może przekonwertować datę opartą na łańcuchu na datę.

Czy jest coś, co robi to za pomocą Float bazującego na String

W przeciwnym razie utknąłem z użyciem CodingKey, aby wprowadzić ciąg i użyć obliczenia get:

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

To sprawia, że ​​robię więcej prac konserwacyjnych, niż się wydaje.

Czy jest to najprostszy sposób, czy jest coś podobnego do DateDecodingStrategy dla innych konwersji typu?

Aktualizacja: Powinienem zwrócić uwagę na to, że przeszedłem także drogę nadrzędności

init(from decoder:Decoder)

Ale to jest w przeciwnym kierunku, ponieważ zmusza mnie to do zrobienia wszystkiego dla siebie.

Odpowiedzi:

13 dla odpowiedzi nr 1

Niestety, nie sądzę, że taka opcja istnieje w obecnej sytuacji JSONDecoder API. Istnieje tylko opcja, aby konwertować wyjątkowy wartości zmiennoprzecinkowe do i od reprezentacji ciągów znaków.

Innym możliwym rozwiązaniem ręcznego dekodowania jest zdefiniowanie a Codable typ opakowania dla każdego LosslessStringConvertible który może kodować i dekodować od jego String reprezentacja:

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

Następnie możesz po prostu mieć właściwość tego typu i użyć wygenerowanego automatycznie Codable zgodność:

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

Chociaż niestety, teraz musisz mówić w kategoriach taxRate.decoded w celu interakcji z Float wartość.

Jednak zawsze można zdefiniować prostą obliczoną właściwość przekierowania w celu złagodzenia tego:

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

Chociaż to wciąż nie jest tak proste, jak powinno być - mam nadzieję, że późniejsza wersja JSONDecoder Interfejs API będzie zawierał więcej niestandardowych opcji dekodowania lub będzie miał możliwość wyrażania konwersji typów w obrębie Codable Sam interfejs API.

Jednak jedną z zalet tworzenia typu opakowania jest to, że można go również użyć w celu uproszczenia ręcznego dekodowania i kodowania. Na przykład przy ręcznym dekodowaniu:

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 dla odpowiedzi nr 2

Zawsze możesz dekodować ręcznie. Tak, biorąc pod uwagę:

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

Możesz to zrobić:

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

Widzieć Zakoduj i dekoduj ręcznie w Kodowanie i dekodowanie typów niestandardowych.

Ale zgadzam się, że wydaje się, że powinien istnieć bardziej elegancki proces konwersji ciągów równoważny DateDecodingStrategy biorąc pod uwagę, ile źródeł JSON tam niepoprawnie zwraca wartości liczbowe jako łańcuchy.


6 dla odpowiedzi nr 3

Zgodnie z Twoimi potrzebami możesz wybrać jeden z dwóch poniższych sposobów, aby rozwiązać swój problem.


# 1. Za pomocą Decodable init(from:) inicjator

Skorzystaj z tej strategii, gdy musisz dokonać konwersji String do Float dla pojedynczej struktury, wyliczenia lub klasy.

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
}

}

Stosowanie:

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. Korzystanie z modelu pośredniego

Użyj tej strategii, gdy masz wiele kluczy zagnieżdżonych w JSON lub gdy potrzebujesz konwertować wiele kluczy (np. Z String do Float) z twojego 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
}

}

Stosowanie:

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 dla odpowiedzi nr 4

Możesz użyć lazy var aby przekonwertować właściwość na inny typ:

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

private var tax_rate: String
}

Jedną z wad tego podejścia jest to, że nie można zdefiniować let stały, jeśli chcesz uzyskać dostęp taxRate, od kiedy po raz pierwszy uzyskujesz dostęp do niego, mutujesz strukturę.

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

1 dla odpowiedzi nr 5

Wiem, że to naprawdę spóźniona odpowiedź, ale zacząłem pracować Codable tylko kilka dni wstecz. I wpadłem na podobny problem.

Aby przekonwertować ciąg na liczbę zmiennoprzecinkową, możesz napisać rozszerzenie na KeyedDecodingContainer i wywołaj metodę z rozszerzenia z init(from decoder: Decoder){}

W przypadku problemu wymienionego w tym wydaniu, patrz rozszerzenie, które napisałem poniżej;

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

Możesz wywołać tę metodę z init(from decoder: Decoder) metoda. Zobacz przykład poniżej;

init(from decoder: Decoder) throws {

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

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

W rzeczywistości możesz użyć tego podejścia do konwersji dowolnego rodzaju danych na dowolny inny typ. Możesz przekonwertować string to Date, string to bool, string to float, float to int itp.

W rzeczywistości, aby przekonwertować ciąg na obiekt Date, wolę takie podejście JSONEncoder().dateEncodingStrategy ponieważ jeśli napiszesz to poprawnie, możesz uwzględnić różne formaty dat w tej samej odpowiedzi.

Mam nadzieję, że pomogłem.


-1 dla odpowiedzi № 6

wprowadź tutaj opis linkuJak używać JSONDecodable w Swift4
1) uzyskaj odpowiedź JSON i utwórz Struct 2) zgodna z klasą Decodable w Struct 3) Inne kroki w kolejnym projekcie (prosty przykład)