Quick Structures: Processing Multiple Types for a Single Object

I use Swift 4 and try to parse some JSON data, which, apparently, in some cases can have different type values ​​for the same key, for example:

{ "type": 0.0 } 

and

 { "type": "12.44591406" } 

I am actually stuck in defining my struct because I can't figure out how to handle this case, because

 struct ItemRaw: Codable { let parentType: String enum CodingKeys: String, CodingKey { case parentType = "type" } } 

throws "Expected to decode String but found a number instead." and naturally

 struct ItemRaw: Codable { let parentType: Float enum CodingKeys: String, CodingKey { case parentType = "type" } } 

throws "Expected to decode Float but found a string/data instead." respectively.

How can I handle this (and similar) cases when defining my struct ?

+3
json data-structures swift swift4 swift-structs
source share
2 answers

I ran into the same problem when trying to decode / encode an β€œedited” field in a Reddit Listing JSON response. I created a structure that represents the dynamic type that may exist for a given key. A key can have either a logical or an integer.

 { "edited": false } { "edited": 123456 } 

If you only need to decrypt, just do init (from :). If you need to go in both directions, you need to implement the encode (to :) function.

 struct Edited: Codable { let isEdited: Bool let editedTime: Int // Where we determine what type the value is init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // Check for a boolean do { isEdited = try container.decode(Bool.self) editedTime = 0 } catch { // Check for an integer editedTime = try container.decode(Int.self) isEdited = true } } // We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try isEdited ? container.encode(editedTime) : container.encode(false) } } 

Inside the Codable class, I use my structure.

 struct Listing: Codable { let edited: Edited } 

Edit: More specific solution for your scenario.

I recommend using the CodingKey protocol and enumeration to store all properties during decoding. When you create something that matches Codable, the compiler will create a private CodingKeys enumeration for you. This allows you to decide what to do based on the key properties of the JSON object.

Just for example, this is JSON I decryption:

 {"type": "1.234"} {"type": 1.234} 

If you want to convert from a string to a double, because you only need a double value, just decrypt the string and then create a double from it. (This is what Itai Ferber does, you will also have to decode all the properties using try decoder.decode (type: forKey :))

 struct JSONObjectCasted: Codable { let type: Double? init(from decoder: Decoder) throws { // Decode all fields and store them let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum. // First check for a Double do { type = try container.decode(Double.self, forKey: .type) } catch { // The check for a String and then cast it, this will throw if decoding fails if let typeValue = Double(try container.decode(String.self, forKey: .type)) { type = typeValue } else { // You may want to throw here if you don't want to default the value(in the case that it you can't have an optional). type = nil } } // Perform other decoding for other properties. } } 

If you need to save a type along with a value, you can use an enumeration that matches Codable instead of a structure. Then you can simply use the switch statement with the type property of JSONObjectCustomEnum and perform actions based on this case.

 struct JSONObjectCustomEnum: Codable { let type: DynamicJSONProperty } // Where I can represent all the types that the JSON property can be. enum DynamicJSONProperty: Codable { case double(Double) case string(String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // Decode the double do { let doubleVal = try container.decode(Double.self) self = .double(doubleVal) } catch DecodingError.typeMismatch { // Decode the string let stringVal = try container.decode(String.self) self = .string(stringVal) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .double(let value): try container.encode(value) case .string(let value): try container.encode(value) } } } 
+5
source share

One simple solution is to provide an init(from:) implementation that tries to decode the value as String , and if that fails because the type is incorrect, try decoding as Double :

 public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) do { self.parentType = try container.decode(String.self, forKey: .parentType) } catch DecodingError.typeMismatch { let value = try container.decode(Double.self, forKey: .parentType) self.parentType = "\(value)" } } 
+5
source share

All Articles