Using decodable to parse multiple types from a json file? Swift 4 [duplicate] - swift

I have an API that will sometimes return a specific key value (in this case id) in the JSON as an Int and other times it will return that same key value as a String. How do I use codable to parse that JSON?
struct GeneralProduct: Codable {
var price: Double!
var id: String?
var name: String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
I keep getting this error message: Expected to decode String but found a number instead. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}

struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
edit/update:
You can also simply assign nil to your id when your api returns 0:
do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}

This is a possible solution with MetadataType, the nice thing is that can be a general solution not for GeneralProduct only, but for all the struct having the same ambiguity:
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
this is the test:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}

Seamlessly decoding from either Int or String into the same property requires writing some code.
However, thanks to a (somewhat) new addition to the language,(property wrappers), you can make it quite easy to reuse this logic wherever you need it:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
#Flexible var id: Int // note this is an Int
var name: String
}
The property wrapper and its supporting code can be implemented like this:
#propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
Original answer
You can use a wrapper over a string that knows how to decode from any of the basic JSON data types: string, number, boolean:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
You can then use this new type in your struct. One minor disadvantage would be that consumer of the struct will need to make another indirection to access the wrapped string. However that can be avoided by declaring the decoded RelaxedString property as private, and use a computed one for the public interface:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
private enum CodingKeys: String, CodingKey {
case price = "p"
case _id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self._id = id.map(RelaxedString.init)
self.name = name
}
}
Advantages of the above approach:
no need to write custom init(from decoder: Decoder) code, which can become tedious if the number of properties to be decoded increase
reusability - RelaxedString can be seamlessly used in other structs
the fact that the id can be decoded from a string or an int remains an implementation detail, consumers of GeneralProduct don't know/care that the id can come from a string or an int
the public interface exposes string values, which keeps the consumer code simple as it will not have to deal with multiple types of data

I created this Gist which has a ValueWrapper struct that can handle
the following types
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23

Based on #Cristik 's answer, I come with another solution using #propertyWrapper.
#propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
And usage is
struct SomeDTO: Codable {
#StringForcible var id: String?
}
Also works like -I think-
struct AnotherDTO: Codable {
var some: SomeDTO?
}

Related

How to parse json that has multiple type(datatype) of values for single key [duplicate]

I have an API that will sometimes return a specific key value (in this case id) in the JSON as an Int and other times it will return that same key value as a String. How do I use codable to parse that JSON?
struct GeneralProduct: Codable {
var price: Double!
var id: String?
var name: String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
I keep getting this error message: Expected to decode String but found a number instead. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
edit/update:
You can also simply assign nil to your id when your api returns 0:
do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
This is a possible solution with MetadataType, the nice thing is that can be a general solution not for GeneralProduct only, but for all the struct having the same ambiguity:
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
this is the test:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}
Seamlessly decoding from either Int or String into the same property requires writing some code.
However, thanks to a (somewhat) new addition to the language,(property wrappers), you can make it quite easy to reuse this logic wherever you need it:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
#Flexible var id: Int // note this is an Int
var name: String
}
The property wrapper and its supporting code can be implemented like this:
#propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
Original answer
You can use a wrapper over a string that knows how to decode from any of the basic JSON data types: string, number, boolean:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
You can then use this new type in your struct. One minor disadvantage would be that consumer of the struct will need to make another indirection to access the wrapped string. However that can be avoided by declaring the decoded RelaxedString property as private, and use a computed one for the public interface:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
private enum CodingKeys: String, CodingKey {
case price = "p"
case _id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self._id = id.map(RelaxedString.init)
self.name = name
}
}
Advantages of the above approach:
no need to write custom init(from decoder: Decoder) code, which can become tedious if the number of properties to be decoded increase
reusability - RelaxedString can be seamlessly used in other structs
the fact that the id can be decoded from a string or an int remains an implementation detail, consumers of GeneralProduct don't know/care that the id can come from a string or an int
the public interface exposes string values, which keeps the consumer code simple as it will not have to deal with multiple types of data
I created this Gist which has a ValueWrapper struct that can handle
the following types
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23
Based on #Cristik 's answer, I come with another solution using #propertyWrapper.
#propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
And usage is
struct SomeDTO: Codable {
#StringForcible var id: String?
}
Also works like -I think-
struct AnotherDTO: Codable {
var some: SomeDTO?
}

how do you set nil to codable enum when unexpected value in swift iOS

I'm using Codable for my WebRequest response which is returning some predefined string or number. So, I'm using Enums for those. But when some unexpected value arrive to Response at that my Codable fails to decode.
Here some code for better understanding.
class WebUser: Codable, Equatable {
static func == (lhs: WebUser, rhs: WebUser) -> Bool {
return lhs.id == rhs.id
}
...
var mobileNumberPrivacy: CommonPrivacyOption?
var emailPrivacy: CommonPrivacyOption?
var dobPrivacy: CommonPrivacyOption?
...
}
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
//Does not help this optional init function
/*init?(rawValue: Int) {
switch rawValue {
case 1: self = .privacyOnlyMe
case 2: self = .privacyPublic
case 3: self = .privacyFriends
case 4: self = .privacyFriendsOfFriends
default: return nil
}
}*/
}
but sometimes from WebServer I'm getting, 0 for dobPrivacy at that time I'm getting DecodingError.dataCorrupted exception with context Cannot initialize CommonPrivacyOption from invalid Int value 0
As I expect to dobPrivacy nil when I get other values then 1/2/3/4.
EDIT:
let dict1 = [
"id": 2,
"mobileNumberPrivacy": 3,
"emailPrivacy": 4,
"dobPrivacy": 0 // Works perfectly with 1
]
do {
let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
print(context.codingPath)
print(context.debugDescription)
}
catch {
print(error.localizedDescription)
}
I'm using this same Codable WebUser object for Profile detail, search users and many more.
so may be some times one more key will not present in WebRequest's response.
I recommend writing a property wrapper that handles this problem for you.
Specifically, let's write a property wrapper named NilOnDecodingError that turns any DecodingError into nil.
Here's the declaration of NilOnDecodingError:
#propertyWrapper
public struct NilOnDecodingError<Wrapped> {
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: Wrapped?
}
We've defined it to wrap any type, storing an Optional.
Now we can conform it to Decodable when the Wrapped type is Decodable:
extension NilOnDecodingError: Decodable where Wrapped: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
wrappedValue = .some(try container.decode(Wrapped.self))
} catch is DecodingError {
wrappedValue = nil
}
}
}
We probably also want it to be Encodable when the Wrapped type is Encodable:
extension NilOnDecodingError: Encodable where Wrapped: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let value = wrappedValue {
try container.encode(value)
} else {
try container.encodeNil()
}
}
}
Now we can wrap the appropriate fields of WebUser:
class WebUser: Codable {
let id: String
#NilOnDecodingError
var mobileNumberPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var emailPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var dobPrivacy: CommonPrivacyOption?
}
For testing, we'll want to print the fields of the decoded user:
extension WebUser: CustomStringConvertible {
var description: String {
return """
WebUser(
id: \(id),
mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
"""
}
}
Now we can try it out:
let json = """
{
"id": "mrugesh",
"mobileNumberPrivacy": 1,
"emailPrivacy": 2,
"dobPrivacy": 1000
}
"""
let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Output:
WebUser(
id: mrugesh,
mobileNumberPrivacy: privacyOnlyMe,
emailPrivacy: privacyPublic),
dobPrivacy: nil))
You need to create a custom Decodable initializer inside WebUser:
class WebUser: Codable {
var dobPrivacy: CommonPrivacyOption?
// Rest of your properties here.
enum CodingKeys: String, CodingKey {
case dobPrivacy
// Add a case for each property you want to decode here.
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Use optional try to decode your enum so that when the
// decode fails because of wrong Int value, it will assign nil.
dobPrivacy = try? container.decode(CommonPrivacyOption.self, forKey: .dobPrivacy)
}
}
Alternatively, you can implement the Decodable initializer inside CommonPrivacyOption and add an additional case unknown like so:
enum CommonPrivacyOption: Int, Codable {
case privacyOnlyMe = 1
case privacyPublic, privacyFriends, privacyFriendsOfFriends
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
// Try to initialize Self from value, if
// value is not 1, 2, 3, or 4, initialize Self to
// the unknown case.
self = .init(rawValue: value) ?? .unknown
}
}
It looks to me like the compiler selects the wrong init for the enum types, instead of init(rawValue) it uses init(from:) that is the one for decoding (which in a way makes sense)
Here is a solution where we override this behaviour by using a custom init(from) in WebUser that decodes the raw values and then creates an enum item
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
self.mobileNumberPrivacy = mobileNumberPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
self.emailPrivacy = emailPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
self.dobPrivacy = dobPrivacy
}
}
Below is a small example
extension WebUser: CustomStringConvertible {
var description: String {
"Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
}
}
let data = """
{
"mobileNumberPrivacy": 1,
"dobPrivacy": 0
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(WebUser.self, from: data)
print(result)
} catch {
print(error)
}
Mobile: Optional(1), email: nil, dob: nil
Of course if you can change your mind about 0 being translated to nil then I would suggest you extend the enum to support the 0 value
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case none = 0
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Then it should work out of the box and you don't need to write any custom code.
I liked #alobaili's answer since it's simple and provided a good solution. One thing I wanted to improve is to make it more generic, so that any Decodable (and Codable) could do it with less code to write.
extension Decodable where Self: RawRepresentable, RawValue: Decodable {
static func initializedOptionalWith(decoder: Decoder, defaultValue: Self) throws -> Self {
let container = try decoder.singleValueContainer()
let value = try container.decode(RawValue.self)
return .init(rawValue: value) ?? defaultValue
}
}
Usage:
init(from decoder: Decoder) throws {
self = try .initializedOptionalWith(decoder: decoder, defaultValue: .unknown)
}
Thank you Rob Mayoff for a great answer. I would like to add just one thing - if the property is missing, decoding fails even if the property is optional. To prevent this failure, add the following extension:
extension KeyedDecodingContainer {
func decode<T>(_ type: NilOnDecodingError<T>.Type, forKey key: Self.Key) throws -> NilOnDecodingError<T> where T: Decodable {
try decodeIfPresent(type, forKey: key) ?? NilOnDecodingError<T>(wrappedValue: nil)
}
}

How to write custom JSON Decoder strategy for Double type in Swift [duplicate]

I have an API that will sometimes return a specific key value (in this case id) in the JSON as an Int and other times it will return that same key value as a String. How do I use codable to parse that JSON?
struct GeneralProduct: Codable {
var price: Double!
var id: String?
var name: String!
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
I keep getting this error message: Expected to decode String but found a number instead. The reason that it returns a number is because the id field is empty and when the id field is empty it defaults to returning 0 as an ID which codable identifies as a number. I can basically ignore the ID key but codable does not give me the option to ignore it to my knowledge. What would be the best way to handle this?
Here is the JSON. It is super simple
Working
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
Error - because there is no id in the system, it returns 0 as a default which codable obviously sees as a number opposed to string.
{
"p":2.19,
"i":0,
"n":"Black Shirt"
}
struct GeneralProduct: Codable {
var price: Double?
var id: String?
var name: String?
private enum CodingKeys: String, CodingKey {
case price = "p", id = "i", name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
price = try container.decode(Double.self, forKey: .price)
name = try container.decode(String.self, forKey: .name)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""
let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""
do {
let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
print(product.price ?? "nil")
print(product.id ?? "nil")
print(product.name ?? "nil")
} catch {
print(error)
}
edit/update:
You can also simply assign nil to your id when your api returns 0:
do {
let value = try container.decode(Int.self, forKey: .id)
id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
This is a possible solution with MetadataType, the nice thing is that can be a general solution not for GeneralProduct only, but for all the struct having the same ambiguity:
struct GeneralProduct: Codable {
var price:Double?
var id:MetadataType?
var name:String?
private enum CodingKeys: String, CodingKey {
case price = "p"
case id = "i"
case name = "n"
}
init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
self.price = price
self.id = id
self.name = name
}
}
enum MetadataType: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
this is the test:
let decoder = JSONDecoder()
var json = "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // 0
}
json = "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
print(id) // hello world
}
Seamlessly decoding from either Int or String into the same property requires writing some code.
However, thanks to a (somewhat) new addition to the language,(property wrappers), you can make it quite easy to reuse this logic wherever you need it:
// note this is only `Decodable`
struct GeneralProduct: Decodable {
var price: Double
#Flexible var id: Int // note this is an Int
var name: String
}
The property wrapper and its supporting code can be implemented like this:
#propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
var wrappedValue: T
init(from decoder: Decoder) throws {
wrappedValue = try T(container: decoder.singleValueContainer())
}
}
protocol FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws
}
extension Int: FlexibleDecodable {
init(container: SingleValueDecodingContainer) throws {
if let int = try? container.decode(Int.self) {
self = int
} else if let string = try? container.decode(String.self), let int = Int(string) {
self = int
} else {
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
}
}
}
Original answer
You can use a wrapper over a string that knows how to decode from any of the basic JSON data types: string, number, boolean:
struct RelaxedString: Codable {
let value: String
init(_ value: String) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// attempt to decode from all JSON primitives
if let str = try? container.decode(String.self) {
value = str
} else if let int = try? container.decode(Int.self) {
value = int.description
} else if let double = try? container.decode(Double.self) {
value = double.description
} else if let bool = try? container.decode(Bool.self) {
value = bool.description
} else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
You can then use this new type in your struct. One minor disadvantage would be that consumer of the struct will need to make another indirection to access the wrapped string. However that can be avoided by declaring the decoded RelaxedString property as private, and use a computed one for the public interface:
struct GeneralProduct: Codable {
var price: Double!
var _id: RelaxedString?
var name: String!
var id: String? {
get { _id?.value }
set { _id = newValue.map(RelaxedString.init) }
}
private enum CodingKeys: String, CodingKey {
case price = "p"
case _id = "i"
case name = "n"
}
init(price: Double? = nil, id: String? = nil, name: String? = nil) {
self.price = price
self._id = id.map(RelaxedString.init)
self.name = name
}
}
Advantages of the above approach:
no need to write custom init(from decoder: Decoder) code, which can become tedious if the number of properties to be decoded increase
reusability - RelaxedString can be seamlessly used in other structs
the fact that the id can be decoded from a string or an int remains an implementation detail, consumers of GeneralProduct don't know/care that the id can come from a string or an int
the public interface exposes string values, which keeps the consumer code simple as it will not have to deal with multiple types of data
I created this Gist which has a ValueWrapper struct that can handle
the following types
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)
https://gist.github.com/amrangry/89097b86514b3477cae79dd28bba3f23
Based on #Cristik 's answer, I come with another solution using #propertyWrapper.
#propertyWrapper
struct StringForcible: Codable {
var wrappedValue: String?
enum CodingKeys: CodingKey {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
wrappedValue = string
} else if let integer = try? container.decode(Int.self) {
wrappedValue = "\(integer)"
} else if let double = try? container.decode(Double.self) {
wrappedValue = "\(double)"
} else if container.decodeNil() {
wrappedValue = nil
}
else {
throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double."))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
And usage is
struct SomeDTO: Codable {
#StringForcible var id: String?
}
Also works like -I think-
struct AnotherDTO: Codable {
var some: SomeDTO?
}

Dealing with dynamic value types with Swift and Codeable

So i am trying to decode a JSON via a Codeable model object. One of the properties, say series_id, can either be an integer or a string. If empty, it is null.
How do I handle that using the decodable option in Swift? I currently have:
struct ContentItem: Codable {
let content_id: String?
let series_id: String?
let rank: Int
let score: Float
}
In my main code I do:
do {
let object = try self.decoder.decode(T.self, from: data)
DispatchQueue.main.async {
completionHandler(.success(object))
}
} catch {
...
}
Thanks!
You can use an enum for that. So it could be either a String or an Int or Unknown:
extension ContentItem {
enum SeriesId: Codable {
case text(String)
case number(Int)
case unknown
}
}
Then you can implement as a Decodable:
extension ContentItem.SeriesId {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
assertionFailure("Unknown SeriesId type")
self = .unknown
}
}
}
Note that you can expand this to manage whatever Empty means (Since it could be a number as well and empty number is unknown).
Also you can expand it to make it encodable too:
extension ContentItem.SeriesId {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text): try container.encode(text)
case .number(let number): try container.encode(number)
case .unknown: throw NSError(domain: "Unknown case is not encodable", code: -1, userInfo: nil)
}
}
}
So you only need to change the type of the series_id:
struct ContentItem: Codable {
let content_id: String?
let series_id: SeriesId?
let rank: Int
let score: Float
}

How to parse JSON with Decodable protocol when property types might change from Int to String? [duplicate]

This question already has answers here:
Using codable with value that is sometimes an Int and other times a String
(5 answers)
Closed 6 months ago.
I have to decode a JSON with a big structure and a lot of nested arrays.
I have reproduced the structure in my UserModel file, and it works, except with one property (postcode) that is in a nested array (Location) that sometimes is an Int and some other is a String. I don't know how to handle this situation and tried a lot of different solutions.
The last one I've tried is from this blog https://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
And it suggests using generics. But now I can't initialize the Location object without providing a Decoder():
Any help or any different approach would be appreciated.
The API call is this one: https://api.randomuser.me/?results=100&seed=xmoba
This is my UserModel File:
import Foundation
import UIKit
import ObjectMapper
struct PostModel: Equatable, Decodable{
static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
if lhs.userId != rhs.userId {
return false
}
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.body != rhs.body {
return false
}
return true
}
var userId : Int
var id : Int
var title : String
var body : String
enum key : CodingKey {
case userId
case id
case title
case body
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: key.self)
let userId = try container.decode(Int.self, forKey: .userId)
let id = try container.decode(Int.self, forKey: .id)
let title = try container.decode(String.self, forKey: .title)
let body = try container.decode(String.self, forKey: .body)
self.init(userId: userId, id: id, title: title, body: body)
}
init(userId : Int, id : Int, title : String, body : String) {
self.userId = userId
self.id = id
self.title = title
self.body = body
}
init?(map: Map){
self.id = 0
self.title = ""
self.body = ""
self.userId = 0
}
}
extension PostModel: Mappable {
mutating func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
body <- map["body"]
userId <- map["userId"]
}
}
Well it's a common IntOrString problem. You could just make your property type an enum that can handle either String or Int.
enum IntOrString: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
As I have found mismatch of your model that you posted in your question and the one in the API endpoint you pointed to, I've created my own model and own JSON that needs to be decoded.
struct PostModel: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
let postCode: IntOrString
// you don't need to implement init(from decoder: Decoder) throws
// because all the properties are already Decodable
}
Decoding when postCode is Int:
let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": 9999
}
""".data(using: .utf8)!
do {
let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
if case .int(let int) = postModel.postCode {
print(int) // prints 9999
} else if case .string(let string) = postModel.postCode {
print(string)
}
} catch {
print(error)
}
Decoding when postCode is String:
let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": "9999"
}
""".data(using: .utf8)!
do {
let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
if case .int(let int) = postModel.postCode {
print(int)
} else if case .string(let string) = postModel.postCode {
print(string) // prints "9999"
}
} catch {
print(error)
}
You can use generic like this:
enum Either<L, R> {
case left(L)
case right(R)
}
extension Either: Decodable where L: Decodable, R: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let left = try? container.decode(L.self) {
self = .left(left)
} else if let right = try? container.decode(R.self) {
self = .right(right)
} else {
throw DecodingError.typeMismatch(Either<L, R>.self, .init(codingPath: decoder.codingPath, debugDescription: "Expected either `\(L.self)` or `\(R.self)`"))
}
}
}
extension Either: Encodable where L: Encodable, R: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case let .left(left):
try container.encode(left)
case let .right(right):
try container.encode(right)
}
}
}
And then declare postcode: Either<Int, String> and if your model is Decodable and all other fields are Decodable too no extra code would be needed.
If postcode can be both String and Int, you have (at least) two possible solutions for this issue. Firstly, you can simply store all postcodes as String, since all Ints can be converted to String. This seems like the best solution, since it seems highly unlikely that you'd need to perform any numeric operations on a postcode, especially if some postcodes can be String. The other solution would be creating two properties for postcode, one of type String? and one of type Int? and always only populating one of the two depending on the input data, as explained in Using codable with key that is sometimes an Int and other times a String.
The solution storing all postcodes as String:
struct PostModel: Equatable, Decodable {
static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
return lhs.userId == rhs.userId && lhs.id == rhs.id && lhs.title == rhs.title && lhs.body == rhs.body
}
var userId: Int
var id: Int
var title: String
var body: String
var postcode: String
enum CodingKeys: String, CodingKey {
case userId, id, title, body, postcode
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.userId = try container.decode(Int.self, forKey: .userId)
self.id = try container.decode(Int.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.body = try container.decode(String.self, forKey: .body)
if let postcode = try? container.decode(String.self, forKey: .postcode) {
self.postcode = postcode
} else {
let numericPostcode = try container.decode(Int.self, forKey: .postcode)
self.postcode = "\(numericPostcode)"
}
}
}
try this extension
extension KeyedDecodingContainer{
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?{
if let resStr = try? decode(type, forKey: key){
return resStr
}else{
if let resInt = try? decode(Int.self, forKey: key){
return String(resInt)
}
return nil
}
}
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?{
if let resInt = try? decode(type, forKey: key){
return resInt
}else{
if let resStr = try? decode(String.self, forKey: key){
return Int(resStr)
}
return nil
}
}
}
example
struct Foo:Codable{
let strValue:String?
let intValue:Int?
}
let data = """
{
"strValue": 1,
"intValue": "1"
}
""".data(using: .utf8)
print(try? JSONDecoder().decode(Foo.self, from: data!))
it will print "Foo(strValue: Optional("1"), intValue: Optional(1))"