Swift: Check if data response is an array or a dictonary - json

I'm calling an API and storing the data in an array.
But if there is no data the debugger says:
Expected to decode Array but found a dictionary instead.
The fail JSON response is:
'{"status":"Failed","msg":"Sorry It\'s Not Working"}'
The success JSON response is:
'[{"id":"509","name":"TEC TEST !"#!12","sortingId":"1"},
{"id":"510","name":"TEC TEST !"#!12","sortingId":"2"},
{"id":"511","name":"TEC TEST !"#!12","sortingId":"3"},
{"id":"512","name":"TEC TEST !"#!12","sortingId":"4"},
{"id":"513","name":"TEC TEST !"#!12","sortingId":"5"},
{"id":"514","name":"TEC TEST !"#!12","sortingId":"6"},
{"id":"519","name":"TEC TEST !"#!12","sortingId":"7"}]'
So I want to switch between fetching my response as
var result:[Items]?
and
var result:Items?
if the Failed JSON gets send
I've been google´ing and searching Stackoverflow without luck
Is there an solution to say if JSON is an array or dictionary?
My Struct:
struct Items: Codable {
let id: String?
let sortingId: String?
let name: String?
let response: String?
let status: String?
let msg: String?
}
My processing of the response:
var result:[Items]?
result = try JSONDecoder().decode([Items].self, from: data!)
DispatchQueue.main.async {
for item in result! {
self.itemArray.append((name: item.name!, id: Int(item.id!)!, sortingId: Int(item.sortingId!)!))
}
}

One solution is to write a custom initializer which conditionally decodes the array or the dictionary.
Please ask the owner of the service to send more consistent JSON. It's very bad. At least the object should be always a dictionary with key status and either the array for key items or the key msg.
This code first tries to decode the array with unkeyedContainer. If it fails it decodes the dictionary.
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ItemData : Decodable {
private enum CodingKeys: String, CodingKey { case status, msg }
let status : String?
let msg: String?
var items = [Item]()
init(from decoder: Decoder) throws {
do {
var unkeyedContainer = try decoder.unkeyedContainer()
while !unkeyedContainer.isAtEnd {
items.append(try unkeyedContainer.decode(Item.self))
}
status = nil; msg = nil
} catch DecodingError.typeMismatch {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decodeIfPresent(String.self, forKey: .status)
msg = try container.decodeIfPresent(String.self, forKey: .msg)
}
}
}
And call it
result = try JSONDecoder().decode(ItemData.self, from: data!)
A – probably more suitable – alternative is to catch the error in the JSONDecoder().decode line and use two simple structs
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ErrorData : Decodable {
let status : String
let msg: String
}
and call it
do {
let decoder = JSONDecoder()
do {
let result = try decoder.decode([Item].self, from: data!)
print(result)
} catch DecodingError.typeMismatch {
let result = try decoder.decode(ErrorData.self, from: data!)
print(result)
}
} catch { print(error) }
A big benefit is that all properties can be non-optional.

First of all the JSON does not contain any array. It's very very easy to read JSON. There are only 2 (two!) collection types, array [] and dictionary {}. As you can see there are no square brackets at all in the JSON string.
it will helpful

Related

Parse JSON as array of objects instead of dictionary using Codable in Swift

I receive the following JSON from API
{
"someProperty":"someValue",
"type":"type1",
"values":{
"first":"someValue",
"second":"someValue" // keys and values are always different !
}
}
I created model to parse this JSON and it works.
struct MyModel: Codable {
let someProperty: String
let type: SomeType
var values: [String: String]?
}
enum SomeType: Codable {
case type1, type2
}
But I need to get array of objects instead of dictionary. Because I need to get values in right order.
I want something like this:
struct MyModel: Codable {
let someProperty: String
let type: SomeType
var values: [Value]?
}
enum SomeType: Codable {
case type1, type2
}
struct Value: Codable {
let key: String
let value: String
}
And I don't understand what should I change in my code to make this model works. Because now I get error "Expected to decode Array but found a dictionary instead."
Is there a way to parse JSON like I want?
Any suggestions? Maybe example of code with similar parsing
Thanks for your time and your help!
Create a custom init. In it decode the dictionary and map it into an array of your Value:
struct MyModel: Codable {
let someProperty: String
let type: SomeType
var values: [Value]?
enum CodingKeys: CodingKey{
case someProperty, type, values
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
someProperty = try container.decode(String.self, forKey: .someProperty)
type = try container.decode(SomeType.self, forKey: .type)
let dictionary = try container.decodeIfPresent([String:String].self, forKey: .values)
values = dictionary?.map{Value(key: $0.key, value: $0.value)}
}
}

How to fix? Expected to decode Dictionary<String, Any> but found a string/data instead

What is wrong here? Or how else I should decode, I would NOT use JSONSerialize.
let jsonData = try! Data(contentsOf: urls[0])
let decoder = JSONDecoder()
let d = try decoder.decode([String: JSON].self, from: jsonData)
file content is a simple JSON:
{"name": "fff", "price": 10}
And my JSON code:
public enum JSON: Decodable {
case string(String)
case number(Float)
case object([String:JSON])
case array([JSON])
case bool(Bool)
}
You need to add a custom init(from:) where you try to decode into each possible enum case until you are successful or throw an error
Here is a short version that handles three of the cases
struct EnumDecoderError: Error {}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = JSON.string(string)
} else if let number = try? container.decode(Float.self) {
self = JSON.number(number)
} else if let array = try? container.decode([JSON].self) {
self = JSON.array(array)
} else {
throw EnumDecoderError()
}
}
as mentioned in the comments by #LeoDabus we can catch typeMismatch errors (and throw any other error directly) or as before throw an error at the end if no decoding worked. (Again a shortened version)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let string = try container.decode(String.self)
self = JSON.string(string)
} catch DecodingError.typeMismatch {
do {
let number = try container.decode(Float.self)
self = JSON.number(number)
} catch DecodingError.typeMismatch {
do {
let array = try container.decode([JSON].self)
self = JSON.array(array)
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(JSON.self, .init(codingPath: decoder.codingPath, debugDescription: "Data type is not supported"))
}
}
}
}
First of all you don't need to maintain datatypes in the JSON enum to parse your response.
The JSONDecoder will be able to parse with the appropriate datatype if you match your object to the response structure that you receive from APIs or JSON files maintained locally
Taking you json file as example:
{"name": "fff", "price": 10}
The recommended way to parse this structure would be as follows
Create a struct or class according to your needs. For this I will use a struct
struct Product: Decodable {
var name: String?
var price: Int?
}
I have marked both the vars optionals just in case to handle the failures if the field does not exist in the JSON response.
Parse the Structure
Use the product struct that was created in the previous step by creating a decoder instance and setting the Product.Self to parse the object
let decoder = JSONDecoder()
let productObject = try decoder.decode(Product.self, from: jsonData)
If you have an array of objects of the same structure in the JSON response use below:
let productObjects = try decoder.decode([Product].self, from: jsonData)
Just include [] around the product object
You need to decode it into a structure
private struct MyData: Codable {
var name: String?
var price: Int?
}
...
let jsonData = try! Data(contentsOf: urls[0])
let d = try JSONDecoder().decode(MyData.self, from: jsonData)
...

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?
}

decoding a json to generic array or class in swift

How do you decode json to a generic model in swift?
In java for decoding json I use GSON and in general it does not matter I use <T<E>> or ArrayList<E>.In swift Array is a struct and can't be inheritance and it has not implemented Decodable.
I'm looking for a generic elegant class to use in all my web service.
My scenario:
I have json response
{
"status": true,
"message": "",
"code": 200,
"response": [{
"id": 43
}]
}
and a generic reponse model like this from web services:
class GeneralResponse< T : Decodable >:NSObject,Decodable{
var status = false
var message = ""
var code = -1
var response : T?
private enum CodingKeys: String, CodingKey {
case status
case message
case code
case response
}
required public init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(Bool.self, forKey: .status)
message = try container.decode(String.self, forKey: .message)
code = try container.decode(Int.self, forKey: .code)
response = try container.decode(T.self, forKey: .response)
}
}
class ItemDemoModel:Decodable {
var id = -1
private enum ItemDemModelCodingKeys : String, CodingKey {
case id
}
required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: ItemDemModelCodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
}
}
response variable can be ItemDemoModel or an array of ItemDemoModel.
For example:
It can be GeneralResponse<Array<ItemDemoModel>>>
or GeneralResponse<ItemDemoModel>>
thanks.
If you declare a Decodable properties with same name as the key in json then you don't really need an enum to define Coding keys and an initializer to manually map every property with the key.
Also, there is no need to inherit from NSObject in Swift until you have a specific use case for that. Looking at the declaration, it seems unnecessary so your GeneralResponse can be redeclared as simple as this,
class GeneralResponse<T: Decodable>: Decodable {
var code: Int
var status: Bool
var message: String?
var response : T?
}
Similarly, ItemDemoModel can be declared as this,
class ItemDemoModel: Decodable {
var id: Int
}
Now you can setup your service as below to get the GeneralResponse<T> for any request,
struct RequestObject {
var method: String
var path: String
var params: [String: Any]
}
class WebService {
private let decoder: JSONDecoder
public init(_ decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}
public func decoded<T: Decodable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (GeneralResponse<T>?, Error?) -> Void) {
// Here you should get data from the network call.
// For compilation, we can create an empty object.
let data = Data()
// Now parsing
do {
let response = try self.decoder.decode(GeneralResponse<T>.self, from: data)
completion(response, nil)
} catch {
completion(nil, error)
}
}
}
Usage
let request = RequestObject(method: "GET", path: "https://url.com", params: [:])
WebService().decoded([ItemDemoModel].self, with: request) { (response, error) in
if let items = response?.response {
print(items)
}
}
P.S; You must be used to declare arrays and dictionaries as below,
let array: Array<SomeType>
let dictionary: Dictionary<String: SomeType>
let arrayOfDictionary: Array<Dictionary<String: SomeType>>
But with Swift's type inference, you can declare an array and a dictionary as simple as below,
let array: [SomeType]
let dictionary: [String: SomeType]
let arrayOfDictionary: [[String: SomeType]]
Here you have a function you may want to use in order to decode your JSON:
func decode<T: Decodable>(_ data: Data, completion: #escaping ((T) -> Void)) {
do {
let model = try JSONDecoder().decode(T.self, from: data)
completion(model)
} catch {
log(error.localizedDescription, level: .error)
}
}
So you can just call your function like:
decode(data, completion: { (user: User) in
// Do something with your parsed user struct or whatever you wanna parse
})
I hope this helps :D
Array<T> conforms to Decodable if T conforms to Decodable, so GeneralResponse<[ItemDemoModel]> won't produce any errors.
As shown here:
You can simply do this:
let decoder = JSONDecoder()
let obj = try decoder.decode(type, from: json.data(using: .utf8)!)

Value from a JSON could be Array and Object

I make HTTP requests to a server and I receive JSON documents.
I have the following structure to decode the JSON:
struct DocumenJSON: Codable {
let code: Int?
let description: String?
let value: Value?
}
The problem is that making a request "A" I receive an Object value and making the request "B" an Array of Value, so the struct should be the following:
struct DocumenJSONArray: Codable {
let code: Int?
let description: String?
let value: [Value]?
}
How can I implement this in swift 4 without duplicate code?
} catch let jsonErr {
print("Error serializing json:", jsonErr)
do {
document = try JSONDecoder().decode(DocumenJSON.self, from: data)
user = User.init(password: "", email: document?.value?.email ?? "Empty", givenNames: document?.value?.nickname ?? "Empty", familyName: document?.value?.lastname ?? "Empty", phone: document?.value?.nickname ?? "Empty")
} catch let jsonErr2 {
print("Error serializing json2:", jsonErr2)
}
}
A possible solution is to use a single struct, for instance:
extension DocumenJSON: Decodable {
enum CodingKeys: String, CodingKey {
case code
case description
case valueAsObject
case valueAsArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decodeIfPresent(Int.self, forKey: .code)
description = try values.decodeIfPresent(String.self, forKey: .description)
valueAsObject = try values.decodeIfPresent(Value.self, forKey: .valueAsObject)
valueAsArray = try values.decodeIfPresent(Array.self, forKey: .valueAsArray)
}
}
Now, whenever you call DocumenJSON just check for each property which valueAsObject or valueAsArray is nil.
Probably not the prettiest solution but at least you're not repeating structs.