How to Read value from response model of Codable - json

print(name) gives me
Optional(TestProject.NameUnion.nameElementArray([TestProject.NameElement(firstKey: Optional(["DSR"]))]))
How I can get firstKey value = DSR or name = Deepak Singh Rawat
Please help to parse name or name-> First key value from it
I am using codable class to decode json response from server
My Code to read and display response
do{
if let path = Bundle.main.path(forResource: "jsonfilename", ofType: "json"){
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
let abModel = try JSONDecoder().decode(NameModel.self, from: data)
let abModel = response
let name = abModel?.first?.name
print(name)
}
}catch{
print("fail")
}
My Json value
[ {
"name": [{
"First": [
"DSR"
]
}]
},{
"name": "Deepak Singh Rawat"
}
]
My Model for it
import Foundation
// MARK: - NameModelElement
struct NameModelElement: Codable {
var name: NameUnion?
}
// NameUnion.swift
enum NameUnion: Codable {
case nameElementArray([NameElement])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([NameElement].self) {
self = .nameElementArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(NameUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for NameUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .nameElementArray(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
struct NameElement: Codable {
var firstKey: [String]?
enum CodingKeys: String, CodingKey {
case firstKey = "First"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
firstKey = try values.decodeIfPresent([String].self, forKey: .firstKey)
}
}
typealias NameModel = [NameModelElement]

Related

How can I decode a nested array of multiple types?

This is my JSON
{
"response":[
[
{
"neededValue" : "Something I really need"
},
567890812309,
"Some text. Bla bla bla",
{
"Garbage" : "Garbage I do not need"
}
]
]
}
It contains an array with two dictionaries, an Int and a String. I need to decode the first dictionary. I tried this:
struct response: Decodable {
var response: [[String : String]]
}
But this obviously doesn't capture the String value and the Int value. How can I describe the array? Please help.
import Foundation
let str = """
{
"response":
[
[
{
"neededValue" : "Something I really need"
},
567890812309,
"Some text. Bla bla bla",
{
"Garbage" : "Garbage I don't need"
}
]
]
}
"""
let data = str.data(using: .utf8)!
struct NeededValue: Decodable {
let neededValue: String
}
struct Garbage: Decodable {
let garbage = ""
enum CodingKeys: String, CodingKey {
case garbage = "Garbage"
}
}
struct Response: Decodable {
let neededValue: NeededValue
let intValue: Int
let stringValue: String
let garbageValue: Garbage
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
neededValue = try container.decode(NeededValue.self)
intValue = try container.decode(Int.self)
stringValue = try container.decode(String.self)
garbageValue = try container.decode(Garbage.self)
}
}
struct Container: Decodable {
let response: [Response]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
response = try container.decode([Response].self, forKey: .response)
}
enum CodingKeys: String, CodingKey {
case response
}
}
do {
let res = try JSONDecoder().decode(Container.self, from: data)
print(res)
} catch {
print(error)
}
BTW, to improve the answer using this link stackoverflow.com/a/47215561/5555803 the better approach is this:
struct Response: Decodable {
var value: Any?
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
} else if let string = try? String(from: decoder) {
value = string
} else if let neededValue = try? NeededValue(from: decoder) {
value = neededValue
} else if let garbage = try? Garbage(from: decoder) {
value = garbage
}
}
}
struct ResponseChild: Decodable {
var response: [[Response]]?
}
do {
let res = try JSONDecoder().decode(ResponseChild.self, from: data)
print(res)
} catch {
print(error)
}

How to Add or Remove an object in result of JSON Codable Swift 4 (with dynamic Keys)

i want import JSON in swift with Codable, modify the object by adding or removing object, and export it in JSON.
Here, my structure
class GenericCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
required init?(stringValue: String) { self.stringValue = stringValue }
required init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
}
class ListSpecie : Codable {
var species: [String : Specie]
required init(from decoder: Decoder) throws
{
let container = try decoder.container(keyedBy: GenericCodingKeys.self)
self.species = [String: Specie]()
for key in container.allKeys{
let value = try container.decodeIfPresent(Specie.self, forKey: GenericCodingKeys(stringValue: key.stringValue)!)
self.species[key.stringValue] = value
}
}
}
class Specie : Codable {
var name : String?
var latinName : [String]?
enum CodingKeys: String, CodingKey {
case name = "l"
case latinName = "ll"
}
required init(from decoder: Decoder) throws
{
let sValues = try decoder.container(keyedBy: CodingKeys.self)
name = try sValues.decodeIfPresent(String.self, forKey: .name)
latinName = try sValues.decodeIfPresent(Array<String>.self, forKey: .latinName)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(name, forKey: .name)
try container.encodeIfPresent(latinName, forKey: .latinName)
}
}
Here, the is a code with sample JSON
let myJson = """
{
"especeID1": {
"l": "Ail",
"ll": ["Allium sativum L.","Allium"]
},
"especeID2": {
"l": "Artichaut",
"ll": ["Cynara cardunculus"]
}
}
"""
let jsonDATA = myJson.data(using: .utf8)!
do{
self.jsonResult = try JSONDecoder().decode(ListSpecie.self, from: jsonDATA)
}catch{
print(error.localizedDescription)
}
Here, I want append or remove specie Object on jsonResult
for myspecie in (self.jsonResult?.species)! {
print(myspecie.key + " " + myspecie.value.name!)
}
// Encodage
let encoder = JSONEncoder()
let productJSON = try! encoder.encode(self.jsonResult?.species)
let jsonString = String(data: productJSON, encoding: .utf8)!
Someone could tell me how i can append or remove a specie object in my jsonResult variable .
Thanks a lot for the help you can bring me.
First of all your code is too complicated, most of the code is redundant.
One class (consider a struct) is sufficient
class Specie : Codable {
var name : String?
var latinName : [String]?
enum CodingKeys: String, CodingKey {
case name = "l"
case latinName = "ll"
}
}
If name and latin name is supposed to appear everywhere declare the properties non-optional (remove the question marks).
And decode the JSON
self.jsonResult = try JSONDecoder().decode([String:Specie].self, from: jsonDATA)
jsonResult is now a dictionary ([String:Specie]), you can remove items
self.jsonResult.removeValue(forKey: "especeID2")
or add an item
let newSpecies = Specie()
newSpecies.name = "Potato"
newSpecies.latinName = ["Solanum tuberosum"]
self.jsonResult["especeID3"] = newSpecies
and encode the object
let encoder = JSONEncoder()
let productJSON = try! encoder.encode(self.jsonResult)
let jsonString = String(data: productJSON, encoding: .utf8)!

Sort "allKeys" with mapping

I decode a JSON with dynamic string keys which looks like this:
{
"dynamickey1":
{"infos1": "blabla","infos2": 21},
"dynamickey2":
{"infos1": "blabla","infos2": 12},
... }
They are sorted as I wanted, as I put a list of string in the URL. But when it decodes the dictionary with the code below (EDIT : i put the complete code), I lost the order:
struct Devise {
let nom : String
let EUR: Float
let ETH: Float
}
struct DataPrix {
let devises: [Devise]
struct DataPrixCodingKeys: CodingKey {
let stringValue : String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue : Int? {return nil}
init?(intValue: Int) {return nil}
}
enum DeviseCodingKeys: String, CodingKey {
case ETH
case EUR
}
}
let decoder = JSONDecoder()
extension DataPrix: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy : DataPrixCodingKeys.self)
self.devises = try (container.allKeys).map { key in
let devisesContainer = try container.nestedContainer(keyedBy: DeviseCodingKeys.self, forKey: key)
let deviseName = key.stringValue
let EUR = try devisesContainer.decode(Float.self, forKey: .EUR)
let ETH = try devisesContainer.decode(Float.self, forKey: .ETH)
return Devise(nom: deviseName, EUR: EUR, ETH: ETH)
}
}
}
So is it possible to sort them alphabetically at this point or at least keep the same order I had in my initial list?
This is more likely a problem with the type of container. It seems you are using NSDictionary, which has allKeys of type Any. Any is not Comparable.
The simplest solution is to cast the dictionary to [String: Any]. Then you can use directly container.keys.sorted(), where < is the default comparator.
let jsonString = """
{
"dynamickey1": {"infos1": "blabla","infos2": 21},
"dynamickey2": {"infos1": "blabla","infos2": 12}
}
"""
let jsonData = jsonString.data(using: .utf8)!
if let container = (try? JSONSerialization.jsonObject(with: jsonData, options: [])) as? [String: Any] {
let sortedKeys = container.keys.sorted()
print(sortedKeys)
}
Or, switch to using Codable protocol and decode the JSON type-safely:
let jsonString = """
{
"dynamickey1": {"infos1": "blabla","infos2": 21},
"dynamickey2": {"infos1": "blabla","infos2": 12}
}
"""
let jsonData = jsonString.data(using: .utf8)!
struct Info: Codable {
let infos1: String
let infos2: Int
}
let parsed = try? JSONDecoder().decode([String: Info].self, from: jsonData)
if let container = parsed {
print(container.keys.sorted())
}
EDIT Solution for the updated question:
let jsonString = """
{"XPR": {"EUR":0.6866,"ETH":0.001088}}
"""
let jsonData = jsonString.data(using: .utf8)!
enum Currency: String, CodingKey, Comparable {
case XPR
case EUR
case ETH
public static func < (lhs: Currency, rhs: Currency) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
struct Devise: Decodable {
let name: String
let rates: [Currency: Double]
public init(from decoder: Decoder) throws {
name = decoder.codingPath.last?.stringValue ?? ""
let container = try decoder.container(keyedBy: Currency.self)
var rates: [Currency: Double] = [:]
try container.allKeys.forEach {
rates[$0] = try container.decode(Double.self, forKey: $0)
}
self.rates = rates
}
}
struct DataPrix: Decodable {
let devises: [Devise]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Currency.self)
devises = try container.allKeys.sorted().map {
return try container.decode(Devise.self, forKey: $0)
}
}
}
let decoder = JSONDecoder()
do {
let exchangeRates = try decoder.decode(DataPrix.self, from: jsonData)
print(exchangeRates)
} catch {
print(error)
}
However, if your keys are truly dynamic, you cannot avoid a dictionary and the best would be just to use it:
let decoder = JSONDecoder()
do {
let exchangeRates = try decoder.decode([String: [String: Double]].self, from: jsonData)
print(exchangeRates)
} catch {
print(error)
}
You can sort like this:
let dictionary = ["key1":"test","key0":"","key5":""]
dictionary.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }.map { key in
// code
}
so for your Code you can use Like this
let keys = dic.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }
print( keys.map { (key) -> [String : Devise]? in
guard let value = dic[key] else {return nil}
return [key : value]
}.compactMap({$0}))

JSON Parsing using Decodable

I am trying to parse the following JSON using decodable protocol. I am able to parse string value such as roomName. But I am not able to map/parse owners, admins, members keys correctly. For eg, using below code, I can able to parse if the values in owners/members are coming as an array. But in some cases, the response will come as a string value(see owners key in JSON), but I am not able to map string values.
Note: Values of admins, members, owners can be string or array (see owners and members keys in JSON)
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac" //This can be array or string
},
"admins": null, //This can be array or string
"members": {
"member": [ //This can be array or string
"steve#local.mac",
"mahe#local.mac"
]
}
}
Model:
struct ChatRoom: Codable{
var roomName: String! = ""
var owners: Owners? = nil
var members: Members? = nil
var admins: Admins? = nil
enum RoomKeys: String, CodingKey {
case roomName
case owners
case members
case admins
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
if let member = try? container.decode(Members.self, forKey: .members) {
members = member
}
if let owner = try? container.decode(Owners.self, forKey: .owners) {
owners = owner
}
if let admin = try? container.decode(Admins.self, forKey: .admins) {
admins = admin
}
}
}
//Owner Model
struct Owners:Codable{
var owner: AnyObject?
enum OwnerKeys:String,CodingKey {
case owner
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OwnerKeys.self)
if let ownerValue = try container.decodeIfPresent([String].self, forKey: .owner){
owner = ownerValue as AnyObject
}
else{
owner = try? container.decode(String.self, forKey: .owner) as AnyObject
}
}
func encode(to encoder: Encoder) throws {
}
}
//Member Model
struct Members:Codable{
var member:AnyObject?
enum MemberKeys:String,CodingKey {
case member
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MemberKeys.self)
if let memberValue = try container.decodeIfPresent([String].self, forKey: .member){
member = memberValue as AnyObject
}
else{
member = try? container.decode(String.self, forKey: .member) as AnyObject
}
}
func encode(to encoder: Encoder) throws {
}
}
This should work. I've removed Admin model for simplicity. I'd prefer Owners/Members to be arrays as they can have one or more values which is what they're for, but if you want them to be AnyObject, you can cast them as so like you're already doing in your init(decoder:).
Test data:
var json = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac"
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)
Models:
struct ChatRoom: Codable, CustomStringConvertible {
var roomName: String! = ""
var owners: Owners? = nil
var members: Members? = nil
var description: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try? encoder.encode(self)
return String(data: data!, encoding: .utf8)!
}
enum RoomKeys: String, CodingKey {
case roomName
case owners
case members
case admins
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
members = try container.decode(Members.self, forKey: .members)
owners = try? container.decode(Owners.self, forKey: .owners)
}
}
struct Owners:Codable{
var owner: [String]?
enum OwnerKeys:String,CodingKey {
case owner
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OwnerKeys.self)
if let ownerValue = try? container.decode([String].self, forKey: .owner){
owner = ownerValue
}
else if let own = try? container.decode(String.self, forKey: .owner) {
owner = [own]
}
}
}
struct Members: Codable {
var member:[String]?
enum MemberKeys:String,CodingKey {
case member
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MemberKeys.self)
if let memberValue = try? container.decode([String].self, forKey: .member){
member = memberValue
}
else if let str = try? container.decode(String.self, forKey: .member){
member = [str]
}
}
}
Test:
var decoder = JSONDecoder()
try? print("\(decoder.decode(ChatRoom.self, from: json!))")
Output:
{
"owners" : {
"owner" : [
"anish#local.mac"
]
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
As you are getting some data as Array or String you can parse this underlying Type with the help of an enum. This will reduce some boilerplate codes as well as redundant codes for each Type you define that is able to have Array or String values.
You define an enum like this:
enum ArrayOrStringType: Codable {
case array([String])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .array(container.decode([String].self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(ArrayOrStringType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .array(let array):
try container.encode(array)
case .string(let string):
try container.encode(string)
}
}
}
And then your models go as:
struct ChatRoom: Codable {
let roomName: String
let owners: Owner
let admins: ArrayOrStringType? // as you are likely to get null values also
let members: Member
struct Owner: Codable {
let owner: ArrayOrStringType
}
struct Member: Codable {
let member: ArrayOrStringType
}
}
/// See!! No more customization inside every init(from:)
Now you can parse your data that contains any of your desired type (Array, String)
Test data 1:
// owner having String type
let jsonTestData1 = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish#local.mac"
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)!
Test data 2:
// owner having [String] type
let jsonTestData2 = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": ["anish1#local.mac", "anish2#local.mac"]
},
"admins": null,
"members": {
"member": [
"steve#local.mac",
"mahe#local.mac"
]
}
}
""".data(using: .utf8)!
Decoding process:
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData1)
print(chatRoom)
} catch {
print(error)
}
// will print
{
"owners" : {
"owner" : "anish#local.mac"
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData2)
print(chatRoom)
} catch {
print(error)
}
// will print
{
"owners" : {
"owner" : [
"anish1#local.mac",
"anish2#local.mac"
]
},
"members" : {
"member" : [
"steve#local.mac",
"mahe#local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}
You can even get more out of the structure. Lets say, you want to work with owners only. You will likely try to get the values as Swifty way:
do {
let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:json)
if case .array(let owners) = chatRoom.owners.owner {
print(owners) // ["anish1#local.mac", "anish2#local.mac"]
}
if case .string(let owners) = chatRoom.owners.owner {
print(owners) // "anish#local.mac"
}
} catch {
print(error)
}
Hope this structuring helps a lot more than other typical ways. Plus this is having the explicit consideration of your expected types. Neither it relies on one type (Array only) nor Any/AnyObject type.
I recreated yours models and tested with your JSON and it worked fine. If your backend returns different types in the different cases (business rules), maybe the best way is create separate variables for each case.(imho)
// Model
import Foundation
struct ChatRoom : Codable {
let roomName : String?
let owners : Owners?
let admins : String?
let members : Members?
enum CodingKeys: String, CodingKey {
case roomName = "roomName"
case owners
case admins = "admins"
case members
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
roomName = try values.decodeIfPresent(String.self, forKey: .roomName)
owners = try Owners(from: decoder)
admins = try values.decodeIfPresent(String.self, forKey: .admins)
members = try Members(from: decoder)
}
}
-
// Member Model
import Foundation
struct Members : Codable {
let member : [String]?
enum CodingKeys: String, CodingKey {
case member = "member"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
member = try values.decodeIfPresent([String].self, forKey: .member)
}
}
-
// Owner Model
import Foundation
struct Owners : Codable {
let owner : String?
enum CodingKeys: String, CodingKey {
case owner = "owner"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
owner = try values.decodeIfPresent(String.self, forKey: .owner)
}
}

How to decode a property with type of JSON dictionary in Swift [45] decodable protocol

Let's say I have Customer data type which contains a metadata property that can contains any JSON dictionary in the customer object
struct Customer {
let id: String
let email: String
let metadata: [String: Any]
}
{
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
The metadata property can be any arbitrary JSON map object.
Before I can cast the property from a deserialized JSON from NSJSONDeserialization but with the new Swift 4 Decodable protocol, I still can't think of a way to do that.
Do anyone know how to achieve this in Swift 4 with Decodable protocol?
With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer and KeyedDecodingContainer. You can find a link to my gist here. By using this code you can now decode any Array<Any> or Dictionary<String, Any> with the familiar syntax:
let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)
or
let array: [Any] = try container.decode([Any].self, forKey: key)
Edit: there is one caveat I have found which is decoding an array of dictionaries [[String: Any]] The required syntax is as follows. You'll likely want to throw an error instead of force casting:
let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]
EDIT 2: If you simply want to convert an entire file to a dictionary, you are better off sticking with api from JSONSerialization as I have not figured out a way to extend JSONDecoder itself to directly decode a dictionary.
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
// appropriate error handling
return
}
The extensions
// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a
struct JSONCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
extension KeyedDecodingContainer {
func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
return try container.decode(type)
}
func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
guard contains(key) else {
return nil
}
guard try decodeNil(forKey: key) == false else {
return nil
}
return try decode(type, forKey: key)
}
func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
var container = try self.nestedUnkeyedContainer(forKey: key)
return try container.decode(type)
}
func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
guard contains(key) else {
return nil
}
guard try decodeNil(forKey: key) == false else {
return nil
}
return try decode(type, forKey: key)
}
func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
var dictionary = Dictionary<String, Any>()
for key in allKeys {
if let boolValue = try? decode(Bool.self, forKey: key) {
dictionary[key.stringValue] = boolValue
} else if let stringValue = try? decode(String.self, forKey: key) {
dictionary[key.stringValue] = stringValue
} else if let intValue = try? decode(Int.self, forKey: key) {
dictionary[key.stringValue] = intValue
} else if let doubleValue = try? decode(Double.self, forKey: key) {
dictionary[key.stringValue] = doubleValue
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
dictionary[key.stringValue] = nestedDictionary
} else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
dictionary[key.stringValue] = nestedArray
}
}
return dictionary
}
}
extension UnkeyedDecodingContainer {
mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
var array: [Any] = []
while isAtEnd == false {
// See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays.
if try decodeNil() {
continue
} else if let value = try? decode(Bool.self) {
array.append(value)
} else if let value = try? decode(Double.self) {
array.append(value)
} else if let value = try? decode(String.self) {
array.append(value)
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
array.append(nestedDictionary)
} else if let nestedArray = try? decode(Array<Any>.self) {
array.append(nestedArray)
}
}
return array
}
mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
return try nestedContainer.decode(type)
}
}
I have played with this problem, too, and finally wrote a simple library for working with “generic JSON” types. (Where “generic” means “with no structure known in advance”.) Main point is representing the generic JSON with a concrete type:
public enum JSON {
case string(String)
case number(Float)
case object([String:JSON])
case array([JSON])
case bool(Bool)
case null
}
This type can then implement Codable and Equatable.
You can create metadata struct which conforms to Decodable protocol and use JSONDecoder class to create object from data by using decode method like below
let json: [String: Any] = [
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": [
"link_id": "linked-id",
"buy_count": 4
]
]
struct Customer: Decodable {
let object: String
let id: String
let email: String
let metadata: Metadata
}
struct Metadata: Decodable {
let link_id: String
let buy_count: Int
}
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let decoder = JSONDecoder()
do {
let customer = try decoder.decode(Customer.self, from: data)
print(customer)
} catch {
print(error.localizedDescription)
}
I came with a slightly different solution.
Let's suppose we have something more than a simple [String: Any] to parse were Any might be an array or a nested dictionary or a dictionary of arrays.
Something like this:
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
}
]
}
"""
Well, this is my solution:
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"))
}
}
}
Try it using
let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)
When I found the old answer, I only tested a simple JSON object case but not an empty one which will cause a runtime exception like #slurmomatic and #zoul found. Sorry for this issue.
So I try another way by having a simple JSONValue protocol, implement the AnyJSONValue type erasure struct and use that type instead of Any. Here's an implementation.
public protocol JSONType: Decodable {
var jsonValue: Any { get }
}
extension Int: JSONType {
public var jsonValue: Any { return self }
}
extension String: JSONType {
public var jsonValue: Any { return self }
}
extension Double: JSONType {
public var jsonValue: Any { return self }
}
extension Bool: JSONType {
public var jsonValue: Any { return self }
}
public struct AnyJSONType: JSONType {
public let jsonValue: Any
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let intValue = try? container.decode(Int.self) {
jsonValue = intValue
} else if let stringValue = try? container.decode(String.self) {
jsonValue = stringValue
} else if let boolValue = try? container.decode(Bool.self) {
jsonValue = boolValue
} else if let doubleValue = try? container.decode(Double.self) {
jsonValue = doubleValue
} else if let doubleValue = try? container.decode(Array<AnyJSONType>.self) {
jsonValue = doubleValue
} else if let doubleValue = try? container.decode(Dictionary<String, AnyJSONType>.self) {
jsonValue = doubleValue
} else {
throw DecodingError.typeMismatch(JSONType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep"))
}
}
}
And here is how to use it when decoding
metadata = try container.decode ([String: AnyJSONValue].self, forKey: .metadata)
The problem with this issue is that we must call value.jsonValue as? Int. We need to wait until Conditional Conformance land in Swift, that would solve this problem or at least help it to be better.
[Old Answer]
I post this question on the Apple Developer forum and it turns out it is very easy.
I can do
metadata = try container.decode ([String: Any].self, forKey: .metadata)
in the initializer.
It was my bad to miss that in the first place.
If you use SwiftyJSON to parse JSON, you can update to 4.1.0 which has Codable protocol support. Just declare metadata: JSON and you're all set.
import SwiftyJSON
struct Customer {
let id: String
let email: String
let metadata: JSON
}
I have written an article and repo that helps in adding [String: Any] support for Codable for decoding as well as encoding.
https://medium.com/nerd-for-tech/string-any-support-for-codable-4ba062ce62f2
This improves on decodable aspect and also add encodable support as solution given by in https://stackoverflow.com/a/46049763/9160905
what you will be able to achieve:
json:
sample code:
You might have a look at BeyovaJSON
import BeyovaJSON
struct Customer: Codable {
let id: String
let email: String
let metadata: JToken
}
//create a customer instance
customer.metadata = ["link_id": "linked-id","buy_count": 4]
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(bytes: try! encoder.encode(customer), encoding: .utf8)!)
Here is more generic (not only [String: Any], but [Any] can decoded) and encapsulated approach (separate entity is used for that) inspired by #loudmouth answer.
Using it will look like:
extension Customer: Decodable {
public init(from decoder: Decoder) throws {
let selfContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try selfContainer.decode(.id)
email = try selfContainer.decode(.email)
let metadataContainer: JsonContainer = try selfContainer.decode(.metadata)
guard let metadata = metadataContainer.value as? [String: Any] else {
let context = DecodingError.Context(codingPath: [CodingKeys.metadata], debugDescription: "Expected '[String: Any]' for 'metadata' key")
throw DecodingError.typeMismatch([String: Any].self, context)
}
self.metadata = metadata
}
private enum CodingKeys: String, CodingKey {
case id, email, metadata
}
}
JsonContainer is a helper entity we use to wrap decoding JSON data to JSON object (either array or dictionary) without extending *DecodingContainer (so it won't interfere with rare cases when a JSON object is not meant by [String: Any]).
struct JsonContainer {
let value: Any
}
extension JsonContainer: Decodable {
public init(from decoder: Decoder) throws {
if let keyedContainer = try? decoder.container(keyedBy: Key.self) {
var dictionary = [String: Any]()
for key in keyedContainer.allKeys {
if let value = try? keyedContainer.decode(Bool.self, forKey: key) {
// Wrapping numeric and boolean types in `NSNumber` is important, so `as? Int64` or `as? Float` casts will work
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(Int64.self, forKey: key) {
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(Double.self, forKey: key) {
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(String.self, forKey: key) {
dictionary[key.stringValue] = value
} else if (try? keyedContainer.decodeNil(forKey: key)) ?? false {
// NOP
} else if let value = try? keyedContainer.decode(JsonContainer.self, forKey: key) {
dictionary[key.stringValue] = value.value
} else {
throw DecodingError.dataCorruptedError(forKey: key, in: keyedContainer, debugDescription: "Unexpected value for \(key.stringValue) key")
}
}
value = dictionary
} else if var unkeyedContainer = try? decoder.unkeyedContainer() {
var array = [Any]()
while !unkeyedContainer.isAtEnd {
let container = try unkeyedContainer.decode(JsonContainer.self)
array.append(container.value)
}
value = array
} else if let singleValueContainer = try? decoder.singleValueContainer() {
if let value = try? singleValueContainer.decode(Bool.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(Int64.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(Double.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(String.self) {
self.value = value
} else if singleValueContainer.decodeNil() {
value = NSNull()
} else {
throw DecodingError.dataCorruptedError(in: singleValueContainer, debugDescription: "Unexpected value")
}
} else {
let context = DecodingError.Context(codingPath: [], debugDescription: "Invalid data format for JSON")
throw DecodingError.dataCorrupted(context)
}
}
private struct Key: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
}
Note that numberic and boolean types are backed by NSNumber, else something like this won't work:
if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil
I have made a pod to facilitate the way the decoding + encoding [String: Any], [Any]. And this provides encode or decode the optional properties, here https://github.com/levantAJ/AnyCodable
pod 'DynamicCodable', '1.0'
How to use it:
import DynamicCodable
struct YourObject: Codable {
var dict: [String: Any]
var array: [Any]
var optionalDict: [String: Any]?
var optionalArray: [Any]?
enum CodingKeys: String, CodingKey {
case dict
case array
case optionalDict
case optionalArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dict = try values.decode([String: Any].self, forKey: .dict)
array = try values.decode([Any].self, forKey: .array)
optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dict, forKey: .dict)
try container.encode(array, forKey: .array)
try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
}
}
Details
Xcode 12.0.1 (12A7300)
Swift 5.3
Based on Tai Le library
// code from: https://github.com/levantAJ/AnyCodable/blob/master/AnyCodable/DecodingContainer%2BAnyCollection.swift
private
struct AnyCodingKey: CodingKey {
let stringValue: String
private (set) var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.intValue = intValue
stringValue = String(intValue)
}
}
extension KeyedDecodingContainer {
private
func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any] {
var values = try nestedUnkeyedContainer(forKey: key)
return try values.decode(type)
}
private
func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any] {
try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key).decode(type)
}
func decode(_ type: [String: Any].Type) throws -> [String: Any] {
var dictionary: [String: Any] = [:]
for key in allKeys {
if try decodeNil(forKey: key) {
dictionary[key.stringValue] = NSNull()
} else if let bool = try? decode(Bool.self, forKey: key) {
dictionary[key.stringValue] = bool
} else if let string = try? decode(String.self, forKey: key) {
dictionary[key.stringValue] = string
} else if let int = try? decode(Int.self, forKey: key) {
dictionary[key.stringValue] = int
} else if let double = try? decode(Double.self, forKey: key) {
dictionary[key.stringValue] = double
} else if let dict = try? decode([String: Any].self, forKey: key) {
dictionary[key.stringValue] = dict
} else if let array = try? decode([Any].self, forKey: key) {
dictionary[key.stringValue] = array
}
}
return dictionary
}
}
extension UnkeyedDecodingContainer {
mutating func decode(_ type: [Any].Type) throws -> [Any] {
var elements: [Any] = []
while !isAtEnd {
if try decodeNil() {
elements.append(NSNull())
} else if let int = try? decode(Int.self) {
elements.append(int)
} else if let bool = try? decode(Bool.self) {
elements.append(bool)
} else if let double = try? decode(Double.self) {
elements.append(double)
} else if let string = try? decode(String.self) {
elements.append(string)
} else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self),
let element = try? values.decode([String: Any].self) {
elements.append(element)
} else if var values = try? nestedUnkeyedContainer(),
let element = try? values.decode([Any].self) {
elements.append(element)
}
}
return elements
}
}
Solution
struct DecodableDictionary: Decodable {
typealias Value = [String: Any]
let dictionary: Value?
init(from decoder: Decoder) throws {
dictionary = try? decoder.container(keyedBy: AnyCodingKey.self).decode(Value.self)
}
}
Usage
struct Model: Decodable {
let num: Double?
let flag: Bool?
let dict: DecodableDictionary?
let dict2: DecodableDictionary?
let dict3: DecodableDictionary?
}
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print(object.dict?.dictionary) // prints [String: Any]
print(object.dict2?.dictionary) // prints nil
print(object.dict3?.dictionary) // prints nil
I used some of the answers on this topic to get the simplest solution possible for me. My problem is that I was receiving a [String: Any] type dictionary, but I could very well work with a [String: String] transforming every other Any value in String. So this is my solution:
struct MetadataType: Codable {
let value: String?
private init(_ value: String?) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let decodedValue = try? container.decode(Int.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(Double.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(Bool.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(String.self) {
self.init(decodedValue)
} else {
self.init(nil)
}
}
}
And when declaring my dictionary, I use
let userInfo: [String: MetadataType]
The easiest and suggested way is to create separate model for each dictionary or model that is in JSON.
Here is what I do
//Model for dictionary **Metadata**
struct Metadata: Codable {
var link_id: String?
var buy_count: Int?
}
//Model for dictionary **Customer**
struct Customer: Codable {
var object: String?
var id: String?
var email: String?
var metadata: Metadata?
}
//Here is our decodable parser that decodes JSON into expected model
struct CustomerParser {
var customer: Customer?
}
extension CustomerParser: Decodable {
//keys that matches exactly with JSON
enum CustomerKeys: String, CodingKey {
case object = "object"
case id = "id"
case email = "email"
case metadata = "metadata"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CustomerKeys.self) // defining our (keyed) container
let object: String = try container.decode(String.self, forKey: .object) // extracting the data
let id: String = try container.decode(String.self, forKey: .id) // extracting the data
let email: String = try container.decode(String.self, forKey: .email) // extracting the data
//Here I have used metadata model instead of dictionary [String: Any]
let metadata: Metadata = try container.decode(Metadata.self, forKey: .metadata) // extracting the data
self.init(customer: Customer(object: object, id: id, email: email, metadata: metadata))
}
}
Usage:
if let url = Bundle.main.url(forResource: "customer-json-file", withExtension: "json") {
do {
let jsonData: Data = try Data(contentsOf: url)
let parser: CustomerParser = try JSONDecoder().decode(CustomerParser.self, from: jsonData)
print(parser.customer ?? "null")
} catch {
}
}
**I have used optional to be in safe side while parsing, can be changed as needed.
Read more on this topic
decode using decoder and coding keys
public let dataToDecode: [String: AnyDecodable]
enum CodingKeys: CodingKey {
case dataToDecode
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.dataToDecode = try container.decode(Dictionary<String, AnyDecodable>.self, forKey: .dataToDecode)
}
This will work
public struct AnyDecodable: Decodable {
public let value: Any
public init<T>(_ value: T?) {
self.value = value ?? ()
}
}
let contentDecodable = try values.decodeIfPresent(AnyDecodable.self, forKey: .content)
extension ViewController {
func swiftyJson(){
let url = URL(string: "https://itunes.apple.com/search?term=jack+johnson")
//let url = URL(string: "http://makani.bitstaging.in/api/business/businesses_list")
Alamofire.request(url!, method: .get, parameters: nil).responseJSON { response in
var arrayIndexes = [IndexPath]()
switch(response.result) {
case .success(_):
let data = response.result.value as! [String : Any]
if let responseData = Mapper<DataModel>().map(JSON: data) {
if responseData.results!.count > 0{
self.arrayExploreStylistList = []
}
for i in 0..<responseData.results!.count{
arrayIndexes.append(IndexPath(row: self.arrayExploreStylistList.count + i, section: 0))
}
self.arrayExploreStylistList.append(contentsOf: responseData.results!)
print(arrayIndexes.count)
}
// if let arrNew = data["results"] as? [[String : Any]]{
// let jobData = Mapper<DataModel>().mapArray(JSONArray: arrNew)
// print(jobData)
// self.datamodel = jobData
// }
self.tblView.reloadData()
break
case .failure(_):
print(response.result.error as Any)
break
}
}
}
}