I had issue with JSON parsing in Swift 4.2. Here is the following code which shown runtime error.
My Json data is as follow which i got from server.
{
code: 406,
message: "Email Address already Exist.",
status: 0
}
I am using Codable to create my structure as follow
struct Registration: Codable {
var code: Int
var status: Int
private enum CodinggKeys: String, CodingKey {
case code
case status
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.code = Int(try container.decode(String.self, forKey: .code))!
} catch DecodingError.typeMismatch {
let value = try container.decode(Double.self, forKey: .code)
self.code = Int(value);
}
do {
self.status = try container.decode(Int.self, forKey: .status)
} catch DecodingError.typeMismatch {
let value = try container.decode(String.self, forKey: .status)
self.status = Int(value);
}
}
}
But every time i got error on parsing status key.
Note: I had tried to parse status in String, Int, Double, Decimal, NSInterger but neither any works. every time i got the same error.
Expected to decode UInt but found a number instead.
The error message is very misleading. This happens when the JSON contains a boolean value, and the struct has an Int property for the corresponding key.
Most likely your JSON actually looks like this:
{
"code": 406,
"message": "Email Address already Exist.",
"status": false
}
and accordingly, your struct should be
struct Registration: Codable {
let code: Int
let status: Bool
}
if let registration = try? JSONDecoder().decode(Registration.self, from: data) {
print(registration.code) // 406
print(registration.status) // false
}
For these kinds of issues, check and make sure that the response and the type of response given in the struct are the same. Here it seems your response is not correct. The status may true or false
{
code: 406,
message: "Email Address already Exist.",
status: 0
}
If the status is true, change your struct as,
struct Registration: Codable {
var code: Int
var status: Bool
var message: String
}
if the status is 0, change the var status: Bool to var status: Int, in the above struct.
You don't need to implement your own decoding initializer if your struct's properties are already Decodable. Neither do you need custom CodingKeys as mentioned by #Gereon.
For the following JSON data:
let data = """
{
"code": 406,
"message": "Email Address already Exist.",
"status": 0
}
""".data(using: .utf8)!
This works fine:
struct Registration: Codable {
var code: Int
var status: Int
}
if let registration = try? JSONDecoder().decode(Registration.self, from: data) {
print(registration.code) // 406
print(registration.status) // 0
}
See Encoding and Decoding Custom Types from Apple for more information.
I think we can use this for resolving such issues:
protocol DecodingHelper {
associatedtype Keys: CodingKey
static func getStringValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> String
static func getIntValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> Int
static func getBoolValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> Bool
}
extension DecodingHelper {
static func getStringValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> String {
var str: String = ""
if let obj = try? container.decode(String.self, forKey: key) {
str = obj
}
else if let obj = try? container.decode(Int.self, forKey: key){
str = String(obj)
}
return str
}
static func getIntValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> Int {
var val: Int = 0
if let obj = try? container.decode(String.self, forKey: key),
let intVal = Int(obj){
val = intVal
}
else if let obj = try? container.decode(Int.self, forKey: key){
val = obj
}
return val
}
static func getBoolValue(_ container: KeyedDecodingContainer<Keys>, key: Keys) -> Bool {
var val: Bool = false
if let obj = try? container.decode(String.self, forKey: key),
let intVal = Int(obj){
(intVal != 0) ? (val = true) : (val = false)
}
else if let obj = try? container.decode(Int.self, forKey: key){
(obj != 0) ? (val = true) : (val = false)
}
else if let obj = try? container.decode(Bool.self, forKey: key){
val = obj
}
return val
}
}
struct VideoFeedback: Codable {
// MARK:- Variables -
var isFeedbackProvided:Bool = true
// MARK:- Initialisation -
private enum CodingKeys: String, DecodingHelper, CodingKey {
typealias Keys = CodingKeys
case isFeedbackProvided = "lastVideoFeedback"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
isFeedbackProvided = CodingKeys.getBoolValue(values, key: .isFeedbackProvided)
}
}
Let me know if you have any suggestion to improve this.
Related
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?
}
I have an App and also a Share Extension. Between them I share data via UserDefaults. But it stopped working all of a sudden. Only bools or Strings can now be retrieved inside the Share Extension but when trying to retrieve a Custom Struct it is always returning nil.
Custom Struct getter/setter in UserDefaults:
//MARK: dataSourceArray
func setDataSourceArray(data: [Wishlist]?){
set(try? PropertyListEncoder().encode(data), forKey: Keys.dataSourceKey)
synchronize()
}
func getDataSourceArray() -> [Wishlist]? {
if let data = self.value(forKey: Keys.dataSourceKey) as? Data {
do {
_ = try PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as [Wishlist]
} catch let error {
print(error)
}
if let dataSourceArray =
try? PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as[Wishlist] {
return dataSourceArray
}
}
return nil
}
I am calling it like this inside my Extension as well as in my Main App:
if let defaults = UserDefaults(suiteName: UserDefaults.Keys.groupKey) {
if let data = defaults.getDataSourceArray() {
print("working")
} else {
print("error getting datasourceArray")
}
}
This is printing "working" in the Main App but "error getting datasourceArray" in my Extension. I don't understand the issue, especially because simple Bool-Getter are working also from my Share Extension, the issue is only with the Custom Struct.
What am I missing here?
Wishlist Struct:
import UIKit
enum PublicState: String, Codable {
case PUBLIC
case PUBLIC_FOR_FRIENDS
case NOT_PUBLIC
}
struct Wishlist: Codable {
var id: String
var name: String
var image: UIImage
var wishes: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
var publicSate: PublicState
enum CodingKeys: String, CodingKey {
case id, name, image, wishData, color, textColor, index, isPublic, isPublicForFriends, publicSate
}
init(id: String, name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int, publicSate: PublicState) {
self.id = id
self.name = name
self.image = image
self.wishes = wishes
self.color = color
self.textColor = textColor
self.index = index
self.publicSate = publicSate
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
wishes = try values.decode([Wish].self, forKey: .wishData)
color = try values.decode(Color.self, forKey: .color).uiColor
textColor = try values.decode(Color.self, forKey: .textColor).uiColor
index = try values.decode(Int.self, forKey: .index)
publicSate = try values.decode(PublicState.self, forKey: .publicSate)
let data = try values.decode(Data.self, forKey: .image)
guard let image = UIImage(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
}
self.image = image
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(wishes, forKey: .wishData)
try container.encode(Color(uiColor: color), forKey: .color)
try container.encode(Color(uiColor: textColor), forKey: .textColor)
try container.encode(index, forKey: .index)
try container.encode(image.pngData(), forKey: .image)
try container.encode(publicSate, forKey: .publicSate)
}
}
Update
This is the part where it fails:
if let data = self.value(forKey: Keys.dataSourceKey) as? Data
Is there any way to catch an error?
I also found out that this feature is actually working for other users. The app is live: https://apps.apple.com/de/app/wishlists-einfach-w%C3%BCnschen/id1503912334
But it is not working for me? I deinstalled the app, downloaded it from the App Store but it is still not working.
I had the same problem but with another type of extension. Hope it works for you too.
Create a file you share between the two targets and put the following code there:
//MARK: - Model
struct WishlistStruct: Codable {
//your wishlist struct, I'll assume you'll have a name and some items
var name : String
var items : [String]
}
typealias Wishlist = WishlistStruct
//MARK: - Defaults
let sharedUserdefaults = UserDefaults(suiteName: SharedDefault.suitName)
struct SharedDefault {
static let suitName = "yourAppGroupHere"
struct Keys{
static let WishlistKey = "WishlistKey"
}
}
var myWishlist: [Wishlist] {
get {
if let data = sharedUserdefaults?.data(forKey: SharedDefault.Keys.WishlistKey) {
let array = try! PropertyListDecoder().decode([Wishlist].self, from: data)
return array
} else{
//Here you should return an error but I didn't find any way to do that so I put this code which hopefully will never be executed
return sharedUserdefaults?.array(forKey: SharedDefault.Keys.WishlistKey) as? [Wishlist] ?? [Wishlist]()
}
} set {
}
}
Now, whenever you need to retrieve the struct, both in app and extension, use the following code:
var wishlist : [Wishlist] = []
var currentWishlist = myWishlist
//In your viewDidLoad call
wishlist.append(contentsOf: myWishlist)
To edit the data inside of your wishlist use the following code
wishlist.append(Wishlist(name: "wishlist", items: ["aaa","bbb","ccc"]))
currentWishlist.append(Wishlist(name: "wishlist", items: items: ["aaa","bbb","ccc"]))
if let data = try? PropertyListEncoder().encode(currentWishlist) {
sharedUserdefaults?.set(data, forKey: SharedDefault.Keys.WishlistKey)
}
Let me know if you need more clarifications
Updated code to your struct. You should change some type of properties to yours(i remove some field for test).
import UIKit
enum PublicState: String, Codable {
case PUBLIC
case PUBLIC_FOR_FRIENDS
case NOT_PUBLIC
}
struct Wishlist: Codable {
var id: String = ""
var name: String = ""
var image: Data = Data()//TODO: use Data type
var color: String = ""//TODO: change it to your class
// var wish: //TODO: add this filed, i don't have it
var textColor: String = "" //TODO: change it to your class
var index: Int = 0
var publicSate: PublicState = .PUBLIC
enum CodingKeys: String, CodingKey {
case id, name, image, color, textColor, index, publicSate
}
init() {}
init(id: String, name: String, image: Data, color: String, textColor: String, index: Int, publicSate: PublicState) {
self.id = id
self.name = name
self.image = image
self.color = color
self.textColor = textColor
self.index = index
self.publicSate = publicSate
}
}
struct WishlistContainer: Codable {
var list: [Wishlist] = []
enum CodingKeys: String, CodingKey {
case list
}
}
class UserDefaultsManager {
//be sure your correctly setup your app groups
private var currentDefaults: UserDefaults = UserDefaults(suiteName: "put here your app group ID")!
private func getFromLocalStorage<T: Codable>(model: T.Type, key: String) -> T? {
if let decoded = currentDefaults.object(forKey: key) as? String {
guard let data = decoded.data(using: .utf8) else { return nil }
if let product = try? JSONDecoder().decode(model.self, from: data) {
return product
}
}
return nil
}
private func saveToLocalStorage(key: String, encodedData: String) {
currentDefaults.set(encodedData, forKey: key)
}
private func removeObject(key: String) {
currentDefaults.removeObject(forKey: key)
}
var wishList: WishlistContainer? {
set {
guard let value = newValue else {
removeObject(key: "wishList")
return
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let jsonData = try? encoder.encode(value) else { return }
guard let jsonString = String(data: jsonData, encoding: .utf8) else { return }
saveToLocalStorage(key: "wishList", encodedData: jsonString)
}
get {
guard let value = getFromLocalStorage(model: WishlistContainer.self, key: "wishList") else {
return nil
}
return value
}
}
}
//MARK: - Usage
let list: [Wishlist] = [Wishlist()]
let container: WishlistContainer = WishlistContainer(list: list)
UserDefaultsManager().wishList = container //set
UserDefaultsManager().wishList // get
I have produced the following sample block of JSON. Any value that ends with a letter is dynamic.
{
"groupName": {
"groupA": {
"fields": {
"fieldA": "valueA",
"fieldB": "valueB"
},
"letters": {
"letterA: "A"
}
},
"groupB": {
"fields": {
"fieldC": "valueC",
"fieldD": "valueD"
},
"letters": {
"letterB: "B"
}
}
}
}
My goal is to use Decodable so that I may read this data into structs that I have defined.
Below is my current work contained in a playground file that I am using to try and resolve this:
import Foundation
let jsonString = "{\"groupName\":{\"groupA\":{\"fields\":{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"},\"letters\":{\"letterA:\"A\"}},\"groupB\":{\"fields\":{\"fieldC\":\"valueC\",\"fieldD\":\"valueD\"},\"letters\":{\"letterB:\"B\"}}}}"
struct CustomCodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let field = CustomCodingKeys.make(key: "field")
static func make(key: String) -> CustomCodingKeys {
return CustomCodingKeys(stringValue: key)!
}
}
// Values
struct Field {
let field: String
let value: String
}
struct Letter: Decodable {
let title: String
let letter: String
}
// Value holders
struct FieldData: Decodable {
var fields: [Field]
init(from decoder: Decoder) throws {
self.fields = [Field]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing field: \(key.stringValue)")
let dynamicKey = CustomCodingKeys.make(key: key.stringValue)
let value = try container.decode(String.self, forKey: dynamicKey)
let field = Field(field: key.stringValue,
value: value)
fields.append(field)
}
}
}
struct LetterData: Decodable {
var letters: [Letter]
init(from decoder: Decoder) throws {
self.letters = [Letter]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing letter: \(key.stringValue)")
let dynamicKey = CustomCodingKeys.make(key: key.stringValue)
let value = try container.decode(String.self, forKey: dynamicKey)
let letter = Letter(title: key.stringValue,
letter: value)
letters.append(letter)
}
}
}
// Containers
struct Group: Decodable {
var name: String!
var groups: [GroupData]
init(from decoder: Decoder) throws {
self.groups = [GroupData]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
print("processing section: \(key.stringValue)")
let group = try container.decode(GroupData.self,
forKey: key)
groups.append(group)
}
}
}
struct GroupData: Decodable {
var fieldData: FieldData
var letterData: LetterData
enum CodingKeys: String, CodingKey {
case fieldData = "fields"
case letterData = "letters"
}
}
struct GroupList: Decodable {
struct GroupName: Decodable {
var name: String!
var groups: [Group]
init(from decoder: Decoder) throws {
self.groups = [Group]()
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
for key in container.allKeys {
let name = key.stringValue
self.name = name
print("processing group: \(String(describing: self.name))")
var group = try container.decode(Group.self,
forKey: key)
group.name = name
groups.append(group)
}
}
}
let groupName: GroupName
}
let decoder = JSONDecoder()
if let data = jsonString.data(using: .utf8),
let groupList = try? decoder.decode(GroupList.self,
from: data) {
print("group list created")
}
In my GroupData struct, I can drop the variables and then implement init(from decoder: Decoder) throws, which when configured with the proper lookups (the FieldData & LetterData inits), can identify the correct pairs. However, it does not populate the proper value structs.
You have small mistake in decoding Group. You tend to decode all keys for group inside Group and also you pass further the to decode GroupData which itself has "fields" and "letters". Use single value container inside Group and it should be fine.
Here is how your Group should look,
struct Group: Decodable {
var name: String!
var groups: GroupData
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
groups = try container.decode(GroupData.self)
}
}
Note, your json itself is incorrect, I have formatted it and it should rather be like this,
let jsonString = "{\"groupName\":{\"groupA\":{\"fields\":{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"},\"letters\":{\"letterA\":\"A\"}},\"groupB\":{\"fields\":{\"fieldC\":\"valueC\",\"fieldD\":\"valueD\"},\"letters\":{\"letterB\":\"B\"}}}}"
I'm currently working on a project that it's API isn't ready yet. So sometimes type of some properties changes. For example I have this struct:
struct Animal: Codable {
var tag: Int?
var name: String?
var type: String?
var birthday: Date?
}
for this json:
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
But in development, the json changes to something like this:
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
So I get some type mismatch error in decoder and nil object. To prevent decoder from failing entire object, I implement a custom init and set nil for any unknown property and it works like charm (NOTE: I'll handle these changes later and this is only for unplanned and temporary changes):
#if DEBUG
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
tag = (try? container.decodeIfPresent(Int.self, forKey: .tag)) ?? nil
name = (try? container.decodeIfPresent(String.self, forKey: .name)) ?? nil
type = (try? container.decodeIfPresent(String.self, forKey: .type)) ?? nil
birthday = (try? container.decodeIfPresent(Date.self, forKey: .birthday)) ?? nil
}
#endif
But for larger classes and struct, I have to write any property manually and it takes time and more importantly, sometimes I missed a property to set!
So is there any way to enumerate over all properties and set them?
I know I can get all keys from container but don't know how to set it's corresponding property:
for key in container.allKeys {
self.<#corresponding_property#> = (try? container.decodeIfPresent(<#corresponding_type#>.self, forKey: key)) ?? nil
}
Thanks
The problem in your particular example is that the type of your values keeps changing: sometimes tag is a string, sometimes it's an integer. You're going to need a lot more than your Optional approach; that deals with whether something is present, not whether it has the right type. You'll need a union type that can decode and represent a string or an integer, like this:
enum Sint : Decodable {
case string(String)
case int(Int)
enum Err : Error { case oops }
init(from decoder: Decoder) throws {
let con = try decoder.singleValueContainer()
if let s = try? con.decode(String.self) {
self = .string(s)
return
}
if let i = try? con.decode(Int.self) {
self = .int(i)
return
}
throw Err.oops
}
}
Using that, I was able to decode both your examples using a single Animal struct type:
struct Animal: Decodable {
var tag: Sint
var name: String
var type: Sint
}
let j1 = """
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
"""
let j2 = """
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
"""
let d1 = j1.data(using: .utf8)!
let a1 = try! JSONDecoder().decode(Animal.self, from: d1)
let d2 = j2.data(using: .utf8)!
let a2 = try! JSONDecoder().decode(Animal.self, from: d2)
Okay, but now let's say you don't even know what the keys are going to be. Then you need an AnyCodingKey type that can mop up the keys no matter what they are, and instead of multiple properties, your Animal will have a single property that's a Dictionary, like this:
struct Animal: Decodable {
var d = [String : Sint]()
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let con = try decoder.container(keyedBy: AnyCodingKey.self)
for key in con.allKeys {
let result = try con.decode(Sint.self, forKey: key)
self.d[key.stringValue] = result
}
}
}
So now you can decode something with complete unknown keys whose value can be string or integer. Again, this works fine against the JSON examples you gave.
Note that this is the inverse of what you originally asked to do. Instead of using the struct property names to generate the keys, I've simply accepted any key of either type and stored it flexibly in the struct through the use of the dictionary. You could also put a property façade in front of that dictionary using the new Swift 4.2 dynamicMemberLookup feature. But that is left as an exercise for the reader!
The tool you want is Sourcery. It's a meta-programming wrapper for Swift that will write the boilerplate for you, since you know what you want to write, it's just tedious (that's what Sourcery is ideal for). The important thing about Sourcery is (despite the name), there's no magic. It just writes Swift code for you based on other Swift code. That makes it easy to pull out later when you don't need it anymore.
Here is my extension.
In my case, If the type does not match, the default will be nil.
extension KeyedDecodingContainer {
public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T: Decodable {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
I'm currently working with Codable types in my project and facing an issue.
struct Person: Codable
{
var id: Any
}
id in the above code could be either a String or an Int. This is the reason id is of type Any.
I know that Any is not Codable.
What I need to know is how can I make it work.
Quantum Value
First of all you can define a type that can be decoded both from a String and Int value.
Here it is.
enum QuantumValue: Decodable {
case int(Int), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
Person
Now you can define your struct like this
struct Person: Decodable {
let id: QuantumValue
}
That's it. Let's test it!
JSON 1: id is String
let data = """
{
"id": "123"
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
JSON 2: id is Int
let data = """
{
"id": 123
}
""".data(using: String.Encoding.utf8)!
if let person = try? JSONDecoder().decode(Person.self, from: data) {
print(person)
}
UPDATE 1 Comparing values
This new paragraph should answer the questions from the comments.
If you want to compare a quantum value to an Int you must keep in mind that a quantum value could contain an Int or a String.
So the question is: what does it mean comparing a String and an Int?
If you are just looking for a way of converting a quantum value into an Int then you can simply add this extension
extension QuantumValue {
var intValue: Int? {
switch self {
case .int(let value): return value
case .string(let value): return Int(value)
}
}
}
Now you can write
let quantumValue: QuantumValue: ...
quantumValue.intValue == 123
UPDATE 2
This part to answer the comment left by #Abrcd18.
You can add this computed property to the Person struct.
var idAsString: String {
switch id {
case .string(let string): return string
case .int(let int): return String(int)
}
}
And now to populate the label just write
label.text = person.idAsString
Hope it helps.
Codable needs to know the type to cast to.
Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.
Otherwise the only way I can think of solving your issue currently is to use generics like below.
struct Person<T> {
var id: T
var name: String
}
let person1 = Person<Int>(id: 1, name: "John")
let person2 = Person<String>(id: "two", name: "Steve")
I solved this issue defining a new Decodable Struct called AnyDecodable, so instead of Any I use AnyDecodable. It works perfectly also with nested types.
Try this in a playground:
var json = """
{
"id": 12345,
"name": "Giuseppe",
"last_name": "Lanza",
"age": 31,
"happy": true,
"rate": 1.5,
"classes": ["maths", "phisics"],
"dogs": [
{
"name": "Gala",
"age": 1
}, {
"name": "Aria",
"age": 3
}
]
}
"""
public struct AnyDecodable: Decodable {
public var value: Any
private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
public init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyDecodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)
You could extend my struct to be AnyCodable if you are interested also in the Encoding part.
Edit: I actually did it.
Here is AnyCodable
struct AnyCodable: Decodable {
var value: Any
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
init(value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
extension AnyCodable: Encodable {
func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}
You can test it With the previous json in this way in a playground:
let stud = try! JSONDecoder().decode(AnyCodable.self, from: jsonData)
print(stud.value as! [String: Any])
let backToJson = try! JSONEncoder().encode(stud)
let jsonString = String(bytes: backToJson, encoding: .utf8)!
print(jsonString)
If your problem is that it's uncertain the type of id as it might be either a string or an integer value, I can suggest you this blog post: http://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/
Basically I defined a new Decodable type
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
From now on, your Person object would be
struct Person: Decodable {
var id: UncertainValue<Int, String>
}
you will be able to access your id using id.value
Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.
Eg:
import AnyCodable
struct Person: Codable
{
var id: AnyCodable
}
To make key as Any, I like all above answers. But when you are not sure which data type your server guy will send then you use Quantum class (as above), But Quantum type is little difficult to use or manage. So here is my solution to make your decodable class key as a Any data type (or "id" for obj-c lovers)
class StatusResp:Decodable{
var success:Id? // Here i am not sure which datatype my server guy will send
}
enum Id: Decodable {
case int(Int), double(Double), string(String) // Add more cases if you want
init(from decoder: Decoder) throws {
//Check each case
if let dbl = try? decoder.singleValueContainer().decode(Double.self),dbl.truncatingRemainder(dividingBy: 1) != 0 { // It is double not a int value
self = .double(dbl)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw IdError.missingValue
}
enum IdError:Error { // If no case matched
case missingValue
}
var any:Any{
get{
switch self {
case .double(let value):
return value
case .int(let value):
return value
case .string(let value):
return value
}
}
}
}
Usage :
let json = "{\"success\":\"hii\"}".data(using: .utf8) // response will be String
//let json = "{\"success\":50.55}".data(using: .utf8) //response will be Double
//let json = "{\"success\":50}".data(using: .utf8) //response will be Int
let decoded = try? JSONDecoder().decode(StatusResp.self, from: json!)
print(decoded?.success) // It will print Any
if let doubleValue = decoded?.success as? Double {
}else if let doubleValue = decoded?.success as? Int {
}else if let doubleValue = decoded?.success as? String {
}
You can replace Any with an enum accepting an Int or a String:
enum Id: Codable {
case numeric(value: Int)
case named(name: String)
}
struct Person: Codable
{
var id: Id
}
Then the compiler will complain about the fact that Id does not conform to Decodable. Because Id has associated values you need to implement this yourself. Read https://littlebitesofcocoa.com/318-codable-enums for an example of how to do this.
Thanks to Luka Angeletti's answer (https://stackoverflow.com/a/48388443/7057338) i've changed enum to struct so we can use it more easily
struct QuantumValue: Codable {
public var string: String?
public var integer: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let int = try? container.decode(Int.self) {
self.integer = int
return
}
if let string = try? container.decode(String.self) {
self.string = string
return
}
throw QuantumError.missingValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
try container.encode(integer)
}
enum QuantumError: Error {
case missingValue
}
func value() -> Any? {
if let s = string {
return s
}
if let i = integer {
return i
}
return nil
}
}
First of all, as you can read in other answers and comments, using Any for this is not good design. If possible, give it a second thought.
That said, if you want to stick to it for your own reasons, you should write your own encoding/decoding and adopt some kind of convention in the serialized JSON.
The code below implements it by encoding id always as string and decoding to Int or String depending on the found value.
import Foundation
struct Person: Codable {
var id: Any
init(id: Any) {
self.id = id
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
if let idstr = try container.decodeIfPresent(String.self, forKey: .id) {
if let idnum = Int(idstr) {
id = idnum
}
else {
id = idstr
}
return
}
fatalError()
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
try container.encode(String(describing: id), forKey: .id)
}
enum Keys: String, CodingKey {
case id
}
}
extension Person: CustomStringConvertible {
var description: String { return "<Person id:\(id)>" }
}
Examples
Encode object with numeric id:
var p1 = Person(id: 1)
print(String(data: try JSONEncoder().encode(p1),
encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"1"}
Encode object with string id:
var p2 = Person(id: "root")
print(String(data: try JSONEncoder().encode(p2),
encoding: String.Encoding.utf8) ?? "/* ERROR */")
// {"id":"root"}
Decode to numeric id:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"2\"}".data(using: String.Encoding.utf8)!))
// <Person id:2>
Decode to string id:
print(try JSONDecoder().decode(Person.self,
from: "{\"id\": \"admin\"}".data(using: String.Encoding.utf8)!))
// <Person id:admin>
An alternative implementation would be encoding to Int or String and wrap the decoding attempts in a do...catch.
In the encoding part:
if let idstr = id as? String {
try container.encode(idstr, forKey: .id)
}
else if let idnum = id as? Int {
try container.encode(idnum, forKey: .id)
}
And then decode to the right type in multiple attempts:
do {
if let idstr = try container.decodeIfPresent(String.self, forKey: .id) {
id = idstr
id_decoded = true
}
}
catch {
/* pass */
}
if !id_decoded {
do {
if let idnum = try container.decodeIfPresent(Int.self, forKey: .id) {
id = idnum
}
}
catch {
/* pass */
}
}
It's uglier in my opinion.
Depending on the control you have over the server serialization you can use either of them or write something else adapted to the actual serialization.
Here your id can be any Codable type:
Swift 4.2
struct Person<T: Codable>: Codable {
var id: T
var name: String?
}
let p1 = Person(id: 1, name: "Bill")
let p2 = Person(id: "one", name: "John")
Swift 5
This is an update about the best answer (IMHO) from Luca Angeletti, so to perform your request:
enum PersonAny: Codable {
case int(Int), string(String) // Insert here the different type to encode/decode
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw AnyError.missingValue
}
enum AnyError:Error {
case missingValue
}
}
// Your declaration
struct Person: Codable
{
var id: PersonAny
}
There is a corner case which is not covered by Luca Angeletti's solution.
For instance, if Cordinate's type is Double or [Double], Angeletti's solution will cause an error: "Expected to decode Double but found an array instead"
In this case, you have to use nested enum instead in Cordinate.
enum Cordinate: Decodable {
case double(Double), array([Cordinate])
init(from decoder: Decoder) throws {
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self = .double(double)
return
}
if let array = try? decoder.singleValueContainer().decode([Cordinate].self) {
self = .array(array)
return
}
throw CordinateError.missingValue
}
enum CordinateError: Error {
case missingValue
}
}
struct Geometry : Decodable {
let date : String?
let type : String?
let coordinates : [Cordinate]?
enum CodingKeys: String, CodingKey {
case date = "date"
case type = "type"
case coordinates = "coordinates"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
date = try values.decodeIfPresent(String.self, forKey: .date)
type = try values.decodeIfPresent(String.self, forKey: .type)
coordinates = try values.decodeIfPresent([Cordinate].self, forKey: .coordinates)
}
}