Swift can't access part of JSON response with enum initializer - json

I have trouble accessing a part of a JSON response.
(part of)The Source:
{
"time":1552726518,
"result":"success",
"errors":null,
"responce":{
"categories":{
"1":{
"nl":{
"name":"New born",
"description":"TEST",
"children":[
{
"name":"Unisex",
"description":"TESTe",
"children":[
{
"name":"Pants",
"description":"TEST",
"children":false
}
]
}
]
}
}
}
}
}
Approach
As you can see the source can have multiple categories. A single categorie will have a 'name', 'description' and may have 'children'. Children wil have a 'name', 'description' and also may have 'children'. This might go endless. If there are no children the SJON statement is 'false'
I use the website: https://app.quicktype.io to generate a junk of code to parse the JSON. I modified the result, because the website doesn't know that the children can go endless:
struct ProductCategories: Codable {
let time: Int?
let result: String?
let errors: [String]?
let responce: ProductCategoriesResponce?
init(time: Int? = nil, result: String? = nil, errors: [String]? = nil, responce: ProductCategoriesResponce? = nil) {
self.time = time
self.result = result
self.errors = errors
self.responce = responce
}
}
struct ProductCategoriesResponce: Codable {
let categories: [String: Category]?
}
struct Category: Codable {
let nl, en: Children?
}
struct Children: Codable {
let name, description: String?
let children: EnChildren?
}
enum EnChildren: Codable {
case bool(Bool)
case childArray([Children])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode([Children].self) {
self = .childArray(x)
return
}
throw DecodingError.typeMismatch(EnChildren.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for EnChildren"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .childArray(let x):
try container.encode(x)
}
}
}
And I can decode de data with:
productCategories = try JSONDecoder().decode(ProductCategories.self, from: jsonData!)
This part works fine. I can access "New Born", but can't get access his children.
I search a long time for the answer I tried so much. To much to all share here. What I expect to get access is:
if let temp = productCategories.responce?.categories?["\(indexPath.item)"]?.nl?.children! {
let x = temp(from: Decoder)
but this will trow me an error:
"Cannot call value of non-function type 'EnChildren'"
also code like:
let temp1 = productCategories.responce?.categories?["\(indexPath.item)"]?.nl?.children
Won't get me anywhere.
So, any ideas? Thank you.

First of all: If you are responsible for the server side, send null for the leaf children or omit the key rather than sending false. It makes the decoding process so much easier.
app.quicktype.io is a great resource but I disagree with its suggestion.
My solution uses a regular struct Children with a custom initializer. This struct can be used recursively.
The key children is decoded conditionally. First decode [Children], on failure decode Bool and set children to nil or hand over the error.
And I declared struct members as much as possible as non-optional
struct Response: Decodable {
let time: Date
let result: String
let errors: [String]?
let responce: ProductCategories
}
struct ProductCategories: Decodable {
let categories: [String: Category]
}
struct Category: Decodable {
let nl: Children
}
struct Children: Decodable {
let name, description: String
let children : [Children]?
private enum CodingKeys : String, CodingKey { case name, description, children }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
do {
children = try container.decode([Children].self, forKey: .children)
} catch DecodingError.typeMismatch {
_ = try container.decode(Bool.self, forKey: .children)
children = nil
}
}
}
In the root object the key time can be decoded to Date if you specify an appropriate date decoding strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(Response.self, from: jsonData!)
And you can access for example the name Unisex with
result.responce.categories["1"]?.nl.children?[0].name

Related

Swift json dynamic key parsing for json

I have json response where only just one key name change rest is same and want to parse without duplicating same struct again.
"attributes": {
"symbol":"EUR",
"name":"Euro",
"precision":2,
}
"attributes":{
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2,
}
How can handle this precision key dynamically in json parsing
You can use a custom keyDecodingStrategy.
Essentially, you write some logic that checks whether the current coding key path matches some criteria, and if it does, map that key to the precision key.
For example:
struct Root : Codable {
let attributes: Attributes
}
struct Attributes : Codable {
let symbol: String
let name: String
let precision: Int
enum CodingKeys: CodingKey {
case symbol
case name
case precision
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({
keys in
// This will decode every precision_for_fiat_price key in the json as "precision".
// You might not want this.
// Make this criteria stricter if you need to. An example is shown below
if keys.last?.stringValue == "precision_for_fiat_price" {
return Attributes.CodingKeys.precision
}
// this will only decode those precision_for_fiat_price that have "attribute" as their parent as "precision"
// if stringPath.suffix(2) == ["attributes", "precision_for_fiat_price"] {
// return Attributes.CodingKeys.precision
// }
return keys.last!
})
let json = """
{
"attributes":{
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2
}
}
""".data(using: .utf8)!
let decoded = try decoder.decode(Root.self, from: json)
If you want to just decode json model like this:
let json = """
{
"attributes": {
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2 // or "precision": 2
}
}
"""
You can create Decodable struct:
struct WrapperModel: Decodable { // any model
var attributes: Attributes
}
struct Attributes : Decodable {
let symbol: String
let name: String
var precision: Int = 0
enum CodingKeys: String, CodingKey, CaseIterable {
case symbol
case name
case precision
case precisionAnother = "precision_for_fiat_price"
// you can write any types of key here
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
symbol = try container.decode(String.self, forKey: .symbol)
name = try container.decode(String.self, forKey: .name)
if let precisionValue = try container.decodeIfPresent(Int.self, forKey: .precision) {
precision = precisionValue
}
if let precisionValue = try container.decodeIfPresent(Int.self, forKey: .precisionAnother) {
precision = precisionValue
}
}
}
You can test it with:
let jsonData = Data(json.utf8)
let decoder = JSONDecoder()
do {
let attributes = try decoder.decode(WrapperModel.self, from: jsonData)
print(attributes)
} catch {
print(error.localizedDescription)
}

Swift Decodable from JSON using wildcard keys [duplicate]

I have a JSON object with incrementing names to parse and I want to store the output into an object with a name field and a list of pet field. I normally use JSONDecoder as its pretty handy and easy to use, but I don't want to hard-code the CodingKey as I think it is very bad practice.
Input:
{"shopName":"KindHeartVet", "pet1":"dog","pet2":"hamster","pet3":"cat", ...... "pet20":"dragon"}
The object that I want to store the result in is something like the following.
class VetShop: NSObject, Decodable {
var shopName: String?
var petList: [String]?
private enum VetKey: String, CodingKey {
case shopName
case petList
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: VetKey.self)
shopName = try? container.decode(String.self, forKey: .shopName)
// implement storing of petList here.
}
}
What I'm struggling a lot on is, as CodingKey is enum, its a let constants, so I can't modify (and shouldn't modify) a constant, but I need to map the petList to the "petN" field, where N is the incrementing number.
EDIT :
I definitely cannot change the API response structure because it is a public API, not something I developed, I'm just trying to parse and get the value from this API, hope this clear the confusion!
Codable has provisions for dynamic keys. If you absolutely can't change the structure of the JSON you're getting, you could implement a decoder for it like this:
struct VetShop: Decodable {
let shopName: String
let pets: [String]
struct VetKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = "\(intValue)";
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: VetKeys.self)
var pets = [String]()
var shopName = ""
for key in container.allKeys {
let str = try container.decode(String.self, forKey: key)
if key.stringValue.hasPrefix("pet") {
pets.append(str)
} else {
shopName = str
}
}
self.shopName = shopName
self.pets = pets
}
}
You can try this to parse your data as Dictionary. In this way, you can get all keys of the dictionary.
let url = URL(string: "YOUR_URL_HERE")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
let dics = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary<String, Any>
let keys = [String](dics.keys)
print(keys) // You have the key list
print(dics[keys[0]]) // this will print the first value
} catch let error as NSError {
print(error)
}
}).resume()
I hope you can figure out what you need to do.

Value from a JSON could be Array and Object

I make HTTP requests to a server and I receive JSON documents.
I have the following structure to decode the JSON:
struct DocumenJSON: Codable {
let code: Int?
let description: String?
let value: Value?
}
The problem is that making a request "A" I receive an Object value and making the request "B" an Array of Value, so the struct should be the following:
struct DocumenJSONArray: Codable {
let code: Int?
let description: String?
let value: [Value]?
}
How can I implement this in swift 4 without duplicate code?
} catch let jsonErr {
print("Error serializing json:", jsonErr)
do {
document = try JSONDecoder().decode(DocumenJSON.self, from: data)
user = User.init(password: "", email: document?.value?.email ?? "Empty", givenNames: document?.value?.nickname ?? "Empty", familyName: document?.value?.lastname ?? "Empty", phone: document?.value?.nickname ?? "Empty")
} catch let jsonErr2 {
print("Error serializing json2:", jsonErr2)
}
}
A possible solution is to use a single struct, for instance:
extension DocumenJSON: Decodable {
enum CodingKeys: String, CodingKey {
case code
case description
case valueAsObject
case valueAsArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decodeIfPresent(Int.self, forKey: .code)
description = try values.decodeIfPresent(String.self, forKey: .description)
valueAsObject = try values.decodeIfPresent(Value.self, forKey: .valueAsObject)
valueAsArray = try values.decodeIfPresent(Array.self, forKey: .valueAsArray)
}
}
Now, whenever you call DocumenJSON just check for each property which valueAsObject or valueAsArray is nil.
Probably not the prettiest solution but at least you're not repeating structs.

Swift 4.1 Codable/Decodable Nested Array

Need some help with more complicated json, with the newest swift4.1 encoder/decoder:
struct:
struct LMSRequest: Decodable {
let id : Int?
let method : String?
let params : [String]?
enum CodingKeys: String, CodingKey {
case id = "id"
case method = "method"
case params = "params"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(Int.self, forKey: .id)
method = try values.decodeIfPresent(String.self, forKey: .method)
params = try values.decodeIfPresent([String].self, forKey: .params)
}}
json:
let json = """
{
"id": 1,
"method": "slim.request",
"params": [
"b8:27:eb:db:6d:62",
[
"serverstatus",
"-",
1,
"tags:GPASIediqtymkovrfijnCYXRTIuwxNlasc"
]
]
}
""".data(using: .utf8)!
code:
let decoder = JSONDecoder()
let lms = try decoder.decode(LMSRequest.self, from: json)
print(lms)
Error is expected to decode string but found array instead. It's coming from the nested array within the "params" array... really stuck on how to build this out, Thanks!
Given what you've described, you should store params as an enum like this:
enum Param: CustomStringConvertible {
case string(String)
case int(Int)
case array([Param])
var description: String {
switch self {
case let .string(string): return string
case let .int(int): return "\(int)"
case let .array(array): return "\(array)"
}
}
}
A param can either be a string, an int, or an array of more params.
Next, you can make Param Decodable by trying each option in turn:
extension Param: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else {
self = .array(try container.decode([Param].self))
}
}
}
Given this, there's no need for custom decoding logic in LMSRequest:
struct LMSRequest: Decodable {
let id : Int?
let method : String?
let params : [Param]?
}
As a side note, I would carefully consider whether these fields are all truly optional. It's very surprising that id is optional, and quite surprising that method is optional, and slightly surprising that params are optional. If they're not really optional, don't make them optional in the type.
From your comments, you're probably misunderstanding how to access enums. params[1] is not a [Param]. It's an .array([Param]). So you have to pattern match it since it might have been a string or an int.
if case let .array(values) = lms.params[1] { print(values[0]) }
That said, if you're doing this a lot, you can make this simpler with extensions on Param:
extension Param {
var stringValue: String? { if case let .string(value) = self { return value } else { return nil } }
var intValue: Int? { if case let .int(value) = self { return value } else { return nil } }
var arrayValue: [Param]? { if case let .array(value) = self { return value } else { return nil } }
subscript(_ index: Int) -> Param? {
return arrayValue?[index]
}
}
With that, you can say things like:
let serverstatus: String? = lms.params[1][0]?.stringValue
Which is probably closer to what you had in mind. (The : String? is just to be clear about the returned type; it's not required.)
For a more complex and worked-out example of this approach, see my generic JSON Decodable that this is a subset of.

Swift 4 JSON Decodable simplest way to decode type change

With Swift 4's Codable protocol there's a great level of under the hood date and data conversion strategies.
Given the JSON:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
I want to coerce it into the following structure
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
The Date Decoding Strategy can convert a String based date into a Date.
Is there something that does that with a String based Float
Otherwise I've been stuck with using CodingKey to bring in a String and use a computing get:
enum CodingKeys: String, CodingKey {
case name, age
case sTaxRate = "tax_rate"
}
var sTaxRate: String
var taxRate: Float { return Float(sTaxRate) ?? 0.0 }
This sort of strands me doing more maintenance than it seems should be needed.
Is this the simplest manner or is there something similar to DateDecodingStrategy for other type conversions?
Update: I should note: I've also gone the route of overriding
init(from decoder:Decoder)
But that is in the opposite direction as it forces me to do it all for myself.
Using Swift 5.1, you may choose one of the three following ways in order to solve your problem.
#1. Using Decodable init(from:) initializer
Use this strategy when you need to convert from String to Float for a single struct, enum or 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
}
}
Usage:
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. Using an intermediate model
Use this strategy when you have many nested keys in your JSON or when you need to convert many keys (e.g. from String to Float) from your 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
}
}
Usage:
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
*/
#3. Using a KeyedDecodingContainer extension method
Use this strategy when converting from some JSON keys' types to your model's property types (e.g. String to Float) is a common pattern in your application.
import Foundation
extension KeyedDecodingContainer {
func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
if let stringValue = try? self.decode(String.self, forKey: key) {
guard let floatValue = Float(stringValue) else {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
return floatValue
} else {
let doubleValue = try self.decode(Double.self, forKey: key)
return Float(doubleValue)
}
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
Usage:
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
*/
Unfortunately, I don't believe such an option exists in the current JSONDecoder API. There only exists an option in order to convert exceptional floating-point values to and from a string representation.
Another possible solution to decoding manually is to define a Codable wrapper type for any LosslessStringConvertible that can encode to and decode from its String representation:
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)
}
}
Then you can just have a property of this type and use the auto-generated Codable conformance:
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"
}
}
Although unfortunately, now you have to talk in terms of taxRate.decoded in order to interact with the Float value.
However you could always define a simple forwarding computed property in order to alleviate this:
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"
}
}
Although this still isn't as a slick as it really should be – hopefully a later version of the JSONDecoder API will include more custom decoding options, or else have the ability to express type conversions within the Codable API itself.
However one advantage of creating the wrapper type is that it can also be used in order to make manual decoding and encoding simpler. For example, with manual decoding:
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
}
}
You can always decode manually. So, given:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
You can do:
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"
}
}
See Encode and Decode Manually in Encoding and Decoding Custom Types.
But I agree, that it seems like there should be a more elegant string conversion process equivalent to DateDecodingStrategy given how many JSON sources out there incorrectly return numeric values as strings.
I know that this is a really late answer, but I started working on Codable couple of days back only. And I bumped into a similar issue.
In order to convert the string to floating number, you can write an extension to KeyedDecodingContainer and call the method in the extension from init(from decoder: Decoder){}
For the problem mentioned in this issue, see the extension I wrote below;
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 {
guard let valueAsString = try? decode(transformFrom, forKey: key),
let value = Float(valueAsString) else {
throw DecodingError.typeMismatch(
type,
DecodingError.Context(
codingPath: codingPath,
debugDescription: "Decoding of \(type) from \(transformFrom) failed"
)
)
}
return value
}
}
You can call this method from init(from decoder: Decoder) method. See an example below;
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self)
}
In fact, you can use this approach to convert any type of data to any other type. You can convert string to Date, string to bool, string to float, float to int etc.
Actually to convert a string to Date object, I will prefer this approach over JSONEncoder().dateEncodingStrategy because if you write it properly, you can include different date formats in the same response.
Hope I helped.
Updated the decode method to return non-optional on suggestion from #Neil.
I used Suran's version, but updated it to return non-optional value for decode(). To me this is the most elegant version. Swift 5.2.
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 {
guard let str = try? decode(transformFrom, forKey: key),
let value = Float(str) else {
throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(type) from \(transformFrom) failed"))
}
return value
}
}
You can use lazy var to convert the property to another type:
struct ExampleJson: Decodable {
var name: String
var age: Int
lazy var taxRate: Float = {
Float(self.tax_rate)!
}()
private var tax_rate: String
}
One disadvantage of this approach is that you cannot define a let constant if you want to access taxRate, since the first time you access it, you are mutating the struct.
// Cannot use `let` here
var example = try! JSONDecoder().decode(ExampleJson.self, from: data)
The options above only deal with the situation that the given field is always String. Many times I've met APIs where the output was once a string, other times number. So this is my suggestion to solve this. It is up to you to alter this to throw exception or set the decoded value to nil.
var json = """
{
"title": "Apple",
"id": "20"
}
""";
var jsonWithInt = """
{
"title": "Apple",
"id": 20
}
""";
struct DecodableNumberFromStringToo<T: LosslessStringConvertible & Decodable & Numeric>: Decodable {
var value: T
init(from decoder: Decoder) {
print("Decoding")
if let container = try? decoder.singleValueContainer() {
if let val = try? container.decode(T.self) {
value = val
return
}
if let str = try? container.decode(String.self) {
value = T.init(str) ?? T.zero
return
}
}
value = T.zero
}
}
struct MyData: Decodable {
let title: String
let _id: DecodableNumberFromStringToo<Int>
enum CodingKeys: String, CodingKey {
case title, _id = "id"
}
var id: Int {
return _id.value
}
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: json.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: jsonWithInt.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}
How to used JSONDecodable in Swift 4:
Get the JSON Response and Create Struct
Conform Decodable class in Struct
Other steps in this GitHub project, a simple example