Generic decoding json into object with optionals - json

I wrote an extension to Decodable with the hopes of having a generic constructor for objects from json strings, it looks like this:
extension Decodable {
init?(with dictionary: [String: Any]) {
guard let data = try? JSONSerialization.data(
withJSONObject: dictionary,
options: .prettyPrinted
) else {
return nil
}
guard let result = try? JSONDecoder().decode(
Self.self,
from: data
) else {
return nil
}
self = result
}
}
An example use case looks like this:
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
guard var goer = Goer(with: json) else { return }
And the object I'm trying to decode into looks like this:
struct Goer: Codable {
let goerId: String
let created: Double
let username: String
let firstName: String
let lastName: String
let city: String
let bio: String
let isPrivate: Bool
let numFollowers: Int
let numFollowing: Int
let followers: [GoerFollow]
let following: [GoerFollow]
}
My issue is that I want to introduce some optionals to these objects that the json strings I'm trying to decode may or may not have keys for. This generic constructor fails in the case where there is no key:value in the json for an optional variable in the object.
I've seen that I can write a custom constructor with a decoder for each object and use the decodeIfPresent function but I wonder if there is a way to do it generically.

if let jsonData = data {
do {
var model = try decoder.decode(Goer.self, from: jsonData)
print("model:\(model)")
} catch {
print("error:\(error)")
}
}
struct Goer: Codable {
let goerId: String?
let created: Double?
let username: String?
let firstName: String?
let lastName: String?
let city: String?
let bio: String?
let isPrivate: Bool?
let numFollowers: Int?
let numFollowing: Int?
let followers: [GoerFollow]?
let following: [GoerFollow]?
}

Related

Converting API JSON data to a Swift struct

I am using Swift for the first time and I'd like to be able to process some info from an API response into a usable Swift object.
I have (for example) the following data coming back from my API:
{
data: [{
id: 1,
name: "Fred",
info: {
faveColor: "red",
faveShow: "Game of Thrones",
faveIceCream: "Chocolate",
faveSport: "Hockey",
},
age: "28",
location: "The Moon",
},{
...
}]
}
In swift I have the data coming back from the API. I get the first object and I'm converting it and accessing it like so:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
let _id = firstObject["id"] as? String ?? "0"
let _name = firstObject["name"] as? String ?? "Unknown"
This is fine until I want to start processing the sub-objects belonging to the first object so I came up with the following structs to try and make this cleaner.
Please note - I don't need to process all of the JSON data coming back so I want to convert it to what I need in the structs
struct PersonInfo : Codable {
let faveColor: String?
let faveShow: String?
}
struct Person : Codable {
let id: String?
let name: String?
let info: PersonInfo?
}
When I take this:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
and then try to convert firstObject to Person or firstObject["info"] to PersonInfo I can't seem to get it to work (I get nil).
let personInfo = firstObject["info"] as? PersonInfo
Can anyone advise please? I just need to get my head around taking API response data and mapping it to a given struct (with sub-objects) ignoring the keys I don't need.
You can simply use decode(_:from:) function of JSONDecoder for this:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode([String: [Person]].self, from: data)
let firstObject = decoded["data"]?.first
} catch {
print(error)
}
Even better you can add another struct to you model like this:
struct PersonsData: Codable {
let data: [Person]
}
And map your JSON using that type:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(PersonsData.self, from: data)
let firstObject = decoded.data.first
} catch {
print(error)
}
Update: Your Person struct might need a little change, because the id property is integer in your JSON.
So, it will end up like this:
struct Person : Codable {
let id: Int?
let name: String?
let info: PersonInfo?
}

How to parse a multi-level json structure in Swift?

The JSON from server looks like this:
A dictionary where the value is another dictionary.
{
"S1": {
"vpn_status": 2,
"vpn_name": "vpn1"
},
"S2": {
"vpn_status": 1,
"vpn_name": "vpn2"
}
}
I have created the following struct to parse it.
public struct ServerStatusResult {
public let vpnName: String
public let status: Int
init?(json: [String: Any]) {
guard
let vpnName = json["vpn_name"] as? String,
let status = json["vpn_status"] as? Int
else {
return nil
}
self.vpnName = vpnName
self.status = status
}
}
And the function to call the server is:
typealias serverStatusCompletedClosure = (_ status: Bool, _ result: Dictionary<String,ServerStatusResult>?, _ error: ServiceError?)->Void
func serverStatus(email: String, password: String, complete: #escaping serverStatusCompletedClosure) {
let url = URL(string: "...")!
try? self.httpClient.get(url: url,
token: "...",
email: email,
password: password)
{ (data, response, error) in
if let error = error {
complete(false, nil, ServiceError.invalidSession)
} else if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
var result: [String:ServerStatusResult]? = nil
result = try! JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<String, ServerStatusResult>
complete(true, result, nil)
This is where my json transformation fails.
Could not cast value of type '__NSDictionaryI' (0x7fff8eaee9b0) to
'app.ServerStatusResult' (0x10021dec0).
What am I missing please?
You can solve it by using Decodable and using a dictionary
First make your struct conform to Decodable
public struct ServerStatusResult: Decodable {
public let vpnName: String
public let status: Int
enum CodingKeys: String, CodingKey {
case vpnName = "vpn_name"
case status = "vpn_status"
}
}
and then the decoding is easy
do {
let result = try JSONDecoder().decode([String: ServerStatusResult].self, from: data)
print(result) //or in you case complete(true, result, nil)
} catch {
print(error)
}
You get an array of dictionary [[String: Any]]
Create a struct for a dictionary and if a dictionary has another dictionary inside it then create another struct for the inner Dictionary and create an object in the outer struct for innner dictionary json.
You can use codeable to parse your json easily, by inheriting your struct with codeable

Decoding JSON with variable parameters using Codable

I have a json response like this
{
"name":"test",
"params":{
"param1":"testA",
"param2":4055,
"param3":9593.34959,
"question":"is this is a test?",
"anything":"testing?",
"random":true
},
"price":103.3
}
My codable struct looks like this
struct:Codable {
var name:String
var params:[String:String]?
var price:Double
}
I have set params to optional because sometimes there are no params but a lot of times there are and codable has an issue because I don't know what types the values in the params dictionary are. I don't even know what the keys are sometimes. I just want to parse them as a dictionary of keys and values with values of type Bool, Int, Double, or String. so a dict like this
let params = ["paramA":1, "param2":"test", "param3":true]
or in the case of the above json this:
let params = ["param1":"testA", "param2":4055, "param3": 9593.34959, "question":"is this is a test?", "anything":"testing?", "random":true]
I am pretty sure I have to create a custom decoder but not exactly sure how to do it.
In your case it's easier to decode json manually like so:
public enum SerializationError: Error {
case wrongRootElement(String)
case missing(String)
case invalid(String, Any)
}
struct YourStruct {
var name:String
var params:[String: String]?
var price:Double
}
extension YourStruct {
public init(json: Any) throws {
guard let jsonDict = json as? [String: Any] else {
throw SerializationError.wrongRootElement("Expected json dictionary not \(json)")
}
guard let name = jsonDict["name"] as? String else {
throw SerializationError.missing("name")
}
let params = jsonDict["params"] as? [String: Any]
let paramsStrs = params?.reduce(into: [String: String]()) { (result, keyValue) in
result[keyValue.key] = String(describing: keyValue.value)
}
guard let price = jsonDict["price"] as? Double else {
throw SerializationError.missing("price")
}
self.init(name: name, params: paramsStrs, price: price)
}
}
let anyYourStruct = try? JSONSerialization.jsonObject(with: jsonData, options: [])
let yourStruct = try? YourStruct(json: anyYourStruct)

Parse Json with a definition at the beginning

I want to parse a Json file with this structure:
{"x":"exchange","b":"usd","ds":["exchange","avgp","mcap","ppc7D","ppc12h","ppc4h","ppc24h"],"data":[["Dow Jones","16360.447","273.89B","6.62","2.14","-0.59","-1.99"],["Dax","877.422","6.80B","38.15","-7.4","-4.44","-4.12"],["nikkei","30.077","2.96B","24.22","-2.3","-4.02","-4.95"],["ATX","281.509","15.29B","214.97","-5.48","-4.58","-10.77"]]}
I do not know how to parse a Json file where there is no definition like:
exchange = "Dow Jones", avgp = 16360.477" etc.
And i could not found anything online.
My code looks like this:
let json = """
{"x":"exchange","b":"usd","ds":["exchange","avgp","mcap","ppc7D","ppc12h","ppc4h","ppc24h"],"data":[["Dow Jones","16360.447","273.89B","6.62","2.14","-0.59","-1.99"],["Dax","877.422","6.80B","38.15","-7.4","-4.44","-4.12"],["nikkei","30.077","2.96B","24.22","-2.3","-4.02","-4.95"],["ATX","281.509","15.29B","214.97","-5.48","-4.58","-10.77"]]}
""".data(using: .utf8)!
struct JsonWebsocket: Decodable {
let exchange: String
let avgp: String
let mcap: String
let ppc7D: String
let ppc12h: String
let ppc4h: String
let ppc24h: String
init(exchange: String, avgp: String, mcap: String, ppc7D: String, ppc12h: String, ppc4h: String, ppc24h: String) {
self.exchange = exchange
self.avgp = avgp
self.mcap = mcap
self.ppc7D = ppc7D
self.ppc12h = ppc24h
self.ppc4h = ppc4h
self.ppc24h = ppc24h
}
}
func fetchJson() -> [String:JsonWebsocket] {
let jsonCoinsDecode = json
let coinDecode = JSONDecoder()
let output = try! coinDecode.decode([String:JsonWebsocket].self, from: jsonCoinsDecode)
return output
}
let array = fetchDataTradingPairs()
But of course it returns an error as the structure does not match the json file.
Does anyone know how to parse this json?
Thanks!
create a struct like that.
struct JsonWebsocket: Decodable {
let x: String
let b: String
let ds: [String]
let data: [[String]]
}
and decode
do {
let coinDecode = JSONDecoder()
let output = try coinDecode.decode(JsonWebsocket.self, from: json)
print(output.data)
}
catch let error{
print(error.localizedDescription)
}
alter native
otherwise, create a custom
struct JsonWebsocket: Decodable {
let exchange: String
let avgp: String
let mcap: String
let ppc7D: String
let ppc12h: String
let ppc4h: String
let ppc24h: String
init(json: [String]) {
self.exchange = json[0]
self.avgp = json[1]
self.mcap = json[2]
self.ppc7D = json[3]
self.ppc12h = json[4]
self.ppc4h = json[5]
self.ppc24h = json[6]
}
}
do {
let jsonData = try JSONSerialization.jsonObject(with: json, options: []) as? [String: Any]
var jsonWebsocket: [JsonWebsocket] = []
if let data = jsonData!["data"] as? [[String]] {
for d in data {
jsonWebsocket.append(JsonWebsocket(json: d))
}
}
print(jsonWebsocket.count)
}
catch let error{
print(error.localizedDescription)
}

How to parsing Json response to Swift objects

Hi i am beginner for swift language and in my project i am using web services and after got response how can i parse below response to Swift object can some on help me please
response:-
[
{
"id" : 1,
"first_name": "John",
"last_name": "Smith",
"age": 25,
"address": {
"id": 1,
"street_address": "2nd Street",
"city": "Bakersfield",
"state": "CA",
"postal_code": 93309
}
}
]
ModelClass:-
class Address:NSObject{
struct Address {
let objID: Int?
let streetAddress: String?
let city: String?
let state: String?
let postalCode: String?
}
struct User {
let objID: Int?
let firstName: String?
let lastName: String?
let age: Int?
let address : Address?
}
}
ViewController:-
func finalResponse(response : AnyObject){
let addressArray = response as! NSArray;
for items in addressArray{
}
}
In swift 4 it get lot easier
Your Model class look like this
Key should be same as json response or make enum for changing the name
struct Address: Decodable {
let objID: Int?
let streetAddress: String?
let city: String?
let state: String?
let postalCode: String?
}
struct User: Decodable {
let objID: Int?
let firstName: String?
let lastName: String?
let age: Int?
let address : Address?
}
}
Your view Controller class look like this
try decoder.decode([User.self], from: jsonData)
This is olden days method in objective-c swift 1,2,3
This is model Class
class SafeJson: NSObject{
override func setValue(_ value: Any?, forKey key: String) {
let firstCharacter = String(key.characters.first!).uppercased()
let range = NSMakeRange(0,1)
let valuex = NSString(string: key).replacingCharacters(in: range, with: firstCharacter)
// let valuex = key.replacingCharacters(in: range, offsetBy: 0), with: firstCharacter)
let selector = NSSelectorFromString("set\(valuex):")
let respond = self.responds(to: selector)
if !respond{
return
}
super.setValue(value, forKey: key)
}
}
class Model:SafeJson{
// var thumbnail_image_name: String?
var title: String?
var number_of_views: NSNumber?
var channel: Channel?
override func setValue(_ value: Any?, forKey key: String) {
if key == "channel"{
self.channel = Channel()
let dictionary = value as! [String: AnyObject]
self.channel?.setValuesForKeys(dictionary)
}else{
super.setValue(value, forKey: key)
}
}
init(dictionary: [String: AnyObject]) {
super.init()
setValuesForKeys(dictionary)
}
}
class Channel:SafeJson{
var name: String?
var profile_image_name: String?
}
In your View controller class You have to pass your response to Model.init it automatically save to model
But in swift 4 setValueForKey is depricated
You have to use decodable for it
warning, did not get to test it so let me know for any warnings and will adjust
For Swift3
func finalResponse(response : AnyObject){
var result: [Address] = []
let json = response as! [String:AnyObject]
// ITERATE THROUGH THE ARRAY OF DICTIONARIES
for item in json {
// a WILL BE THE MAIN OBJECT YOU'RE CREATING
var a = Adress()
a.objID = item["id"] as! Int
.....
// REPEAT FOR EVERY ELEMENT UNTIL YOU REACH NEXT SUBCLASS
.....
// CREATE A NEW DICTIONARY FOR THE SUBCLASS Address
var b = item["address"] as! Dictionary<String, String>
a.address.objID = b["id"] as! Int
// IF YOU DON'T WANT TO CREATE var b YOU CAN WRITE
// a.address.objID = a["address"]["id"] INSTEAD
// ADD YOUR OBJECT TO RESULT
result.append(a)
}
}
Herr is the code to parse data from your JSON data. Create struct at the location that I commented in the code.
do{
let json = try JSONSerialization.jsonObject(with: yourJSONData!, options: []) as? [Any]
let firstUser = json?[0] as? [String: Any]
let id = firstUser?["id"] as? Int
let firstName = firstUser?["first_name"] as? String
//etc... for other keys
let address = firstUser?["address"] as? [String, Any]
let streetAddress = address?["street_address"] as? String
let state = address?["state"] as? String
//etc... create your address struct here and then create the user struct
dump(firstPerson?["lastName"])
}catch let error{
}