Swift json decode with dynamic keys - json

i'm trying to decode a nested json with dynamic keys, but can't find a solution.
here's my json:
{
"available_channels": {
"1000": {
"creation_date": "1111222",
"category_id": "9"
},
"1001": {
"creation_date": "222333",
"category_id": "10"
}
}
as you can see, "1000" and "1001" are dynamique.
The models i'm using:
struct StreamsData: Codable{
let availableChannels: AvailableChannels
}
struct AvailableChannels: Codable{
let channels: [String: Channel]
}
struct Channel: Codable{
let creationDate: String
let categoryId: String
}
'StreamsData' is the root object
'AvailableChannels' is the objects containing all channels objects
'Channel' channel model
decoding the json:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let streams = try decoder.decode(StreamsData.self, from: data)
With this code i have this error:
CodingKeys(stringValue: "availableChannels", intValue: nil)
- debugDescription : "No value associated with key CodingKeys(stringValue: \"channels\", intValue: nil) (\"channels\")."
The problem is clear, as 'AvailableChannels' is declared to have a 'channel' property, the decoder is trying to find "channels" as key for the object containing the "creation_date".
Could you help me to solve this problem, thanks.

You only need
struct StreamsData: Codable{
let availableChannels: [String: Channel]
}
struct Channel: Codable{
let creationDate,categoryId: String
}
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let res = try dec.decode(StreamsData.self, from: data)
}
catch {
print(error)
}

Related

how to parse this complex nested Json

The Json is valid but I m getting nil with below code and struct for the returned json.
problem encountered:
at JSonDecoder.decode() : it returned this error msg:
keyNotFound(CodingKeys(stringValue: "items", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "items", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "items", intValue: nil) ("items").", underlyingError: nil))
How to get these a) image b) location from the Json
Thanks
here the code
func getJsonMapData(){
guard let mapUrl = URL(string: "https://xxxxxx/traffic-images") else { return }
URLSession.shared.dataTask(with: mapUrl) { (data, response, error) in
guard error == nil else { return}
guard let data = data else { return}
//- problem:
do {
let LocationArrDict = try JSONDecoder().decode([String:[Location]].self, from: data)else{
print(LocationArrDict)
} catch {
print(error)
}
}.resume()
}
//------------- return Json String:
{
"items":[
{
"timestamp":"2020-12-05T08:45:43+08:00",
"cameras":[
{
"timestamp":"2020-11-05T08:42:43+08:00",
"image":"https://xxxxxxxxx/traffic-images/2020/12/2ab06cd8-4dcf-434c-b758-804e690e57db.jpg",
"location":{
"latitude":1.29531332,
"longitude":103.871146
},
"camera_id":"1001",
"image_metadata":{
"height":240,
"width":320,
"md5":"c9686a013f3a2ed4af61260811661fc4"
}
},
{
"timestamp":"2020-11-05T08:42:43+08:00",
"image":"https://xxxxxxxxxx/traffic-images/2020/12/9f6d307e-8b05-414d-b27d-bf1414aa2cc7.jpg",
"location":{
"latitude":1.319541067,
"longitude":103.8785627
},
"camera_id":"1002",
"image_metadata":{
"height":240,
"width":320,
"md5":"78060d8fbdd241adf43a2f1ae5d252b1"
}
},
........
{
"timestamp":"2020-12-05T08:42:43+08:00",
"image":"https://xxxxxx/traffic-images/2020/12/98f64fe6-5985-4a8a-852f-0be24b0a6271.jpg",
"location":{
"latitude":1.41270056,
"longitude":103.80642712
},
"camera_id":"9706",
"image_metadata":{
"height":360,
"width":640,
"md5":"f63d54176620fa1d9896fa438b3cc753"
}
}
]
}
],
"api_info":{
"status":"healthy"
}
}
//------------ struct for the return Json result:
// MARK: - Location
struct Location: Codable {
let items: [Item]
let apiInfo: APIInfo
enum CodingKeys: String, CodingKey {
case items
case apiInfo = "api_info"
}
}
// MARK: - APIInfo
struct APIInfo: Codable {
let status: String
}
// MARK: - Item
struct Item: Codable {
let timestamp: Date
let cameras: [Camera]
}
// MARK: - Camera
struct Camera: Codable {
let timestamp: Date
let image: String
let location: LocationClass
let cameraID: String
let imageMetadata: ImageMetadata
enum CodingKeys: String, CodingKey {
case timestamp, image, location
case cameraID = "camera_id"
case imageMetadata = "image_metadata"
}
}
// MARK: - ImageMetadata
struct ImageMetadata: Codable {
let height, width: Int
let md5: String
}
// MARK: - LocationClass
struct LocationClass: Codable {
let latitude, longitude: Double
}
``
Error #1: The type to be decoded is wrong it must be Location.self.
Error #2: To decode the ISO date as Date you have to add the .iso8601 date decoding strategy.
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let locationArrDict = try decoder.decode(Location.self, from: data)
print(locationArrDict)
} catch {
print(error)
}
And you could decode the strings representing an URL directly to URL
You have to create your data models and describe them as the response you expect to receive. I will just give you a brief example based on your json how you would decode it.
First of all in order to decode a JSON using JSONDecoder your models have to conform to Decodable protocol. Then you have to create those models.
struct Item: Decodable {
let timestamp: Date
// let cameras: [...]
}
struct ApiInfo: Decodable {
enum Status: String, Decodable {
case healthy
}
let status: Status
}
struct Response: Decodable {
let items: [Item]
let apiInfo: ApiInfo
}
Then you have to configure your JSONDecoder and decode that JSON. As we can see clearly, your JSON uses snake_case naming convention and that is not the default one for JSONDecoder so you have to set it. Same also applies for the dates - it is used iso8601 standard of representation.
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
Finally, you have you decode it.
do {
let response = try jsonDecoder.decode(Response.self, from: jsonData)
print(response)
} catch {
print(error)
}

Decode a PascalCase JSON with JSONDecoder

I need to decode a JSON with uppercased first letters (aka PascalCase or UppperCamelCase) like this :
{
"Title": "example",
"Items": [
"hello",
"world"
]
}
So I created a model conforming to Codable:
struct Model: Codable {
let title: String
let items: [String]
}
But JSONDecoder raises an error because the case is different.
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "title", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"title\", intValue: nil) (\"title\").", underlyingError: nil))
I would like to keep my model's properties in camelCase but I can't change the JSON fomat.
A nice solution I found is to create a KeyDecodingStrategy similar to the .convertFromSnakeCase available in Foundation.
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromPascalCase: JSONDecoder.KeyDecodingStrategy {
return .custom { keys -> CodingKey in
// keys array is never empty
let key = keys.last!
// Do not change the key for an array
guard key.intValue == nil else {
return key
}
let codingKeyType = type(of: key)
let newStringValue = key.stringValue.firstCharLowercased()
return codingKeyType.init(stringValue: newStringValue)!
}
}
}
private extension String {
func firstCharLowercased() -> String {
prefix(1).lowercased() + dropFirst()
}
}
It can be used easily like so :
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromPascalCase
let model = try! decoder.decode(Model.self, from: json)
Full example on Gist

Deserialization JSON swift 4.2

I try to deserialize my JSON by using Decodable protocol, also i use enum with CodingKey, but it doesn't work. I need only nested array (start with "indicator"), and only few fields (all of them in struct). I tried a lot of different options, but unfortunately..
P.S. Also i tried to do it without CodingKey. Anyway response was: "Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "country", intValue: nil)" Ofc i read this, maybe array is a reason(i mean this strange intValue)?
JSON
[
{
"page":1,
"pages":2,
"per_page":50,
"total":59,
"sourceid":"2",
"lastupdated":"2019-03-21"
},
[
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2018",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2017",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
...
]
]
My code
struct CountryObject: Decodable{
var country: CountryInfo
var date: Int
var value: Int?
private enum RawValues: String, Decodable{
case date = "date"
case vallue = "value"
}
}
struct CountryInfo: Decodable{//Country names
var id: String?
var value: String?
private enum RawValues: String, Decodable{
case id = "id"
case value = "value"
}
}//
let urlString = "https://api.worldbank.org/v2/country/SS/indicator/IP.PAT.RESD?format=json"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) {(data,response,error) in
guard let data = data else {return}
guard error == nil else {return}
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let countryObject = try! decoder.decode([CountryObject].self, from: data)
print(countryObject)
}catch let error{
print(error)
}
}.resume()
Create a root struct and decode the array with unkeyedContainer
struct Root : Decodable {
let info : Info
let countryObjects : [CountryObject]
init(from decoder: Decoder) throws {
var arrayContrainer = try decoder.unkeyedContainer()
info = try arrayContrainer.decode(Info.self)
countryObject = try arrayContrainer.decode([CountryObject].self)
}
}
struct Info : Decodable {
let page, pages, perPage: Int
let lastupdated: String
}
struct CountryObject : Decodable {
let country: CountryInfo
let date: String
let value: Int?
}
struct CountryInfo : Decodable { //Country names
let id: String
let value: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: data)
let countryObjects = root.countryObjects
print(countryObjects)
} catch { print(error) }
(De)serializing the JSON twice is unnecessarily expensive.

Swift 4 Decodable - No value associated with key CodingKeys [duplicate]

This question already has answers here:
How do I use custom keys with Swift 4's Decodable protocol?
(4 answers)
Closed 3 years ago.
I'm decoding a JSON response in my Swift App, and the code used to work till it decided to stop working.
this is my json reposnse
{
"foods": [
{
"food_name": "Milk Chocolate",
"brand_name": "Snickers",
"serving_weight_grams": 41.7,
"nf_calories": 212.3,
"nf_total_fat": 11.6,
"nf_saturated_fat": 4,
"nf_total_carbohydrate": 22.7,
"nf_protein": 3.9
}
]
}
And this is the code to decode my json
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
do {
//Decode dataResponse received from a network request
let decoder = JSONDecoder()
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
self.jsonfood = foods.JSONFood[0]
print(self.jsonfood!)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
And my Structs are
struct JSONFoods: Decodable {
var JSONFood: [JSONFood]
}
struct JSONFood: Decodable{
var food_name: String
var brand_name: String
var nf_calories: Int
var nf_protein: Int
var nf_total_fat: Int
var nf_total_carbohydrate: Int
var serving_weight_grams: Int
}
And the error message I get is this
keyNotFound(CodingKeys(stringValue: "JSONFood", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "JSONFood", intValue: nil) ("JSONFood").", underlyingError: nil))
And if i get replace decode(JSONFoods.self, from: data) with decode(JSONFood.self, from: data)
I get this error message
keyNotFound(CodingKeys(stringValue: "food_name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "food_name", intValue: nil) ("food_name").", underlyingError: nil))
I searched everywhere with no luck, any help is very appreciated
You need
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
enum CodingKeys: String, CodingKey {
case foodName = "food_name"
case brandName = "brand_name"
case servingWeightGrams = "serving_weight_grams"
case nfCalories = "nf_calories"
case nfTotalFat = "nf_total_fat"
case nfSaturatedFat = "nf_saturated_fat"
case nfTotalCarbohydrate = "nf_total_carbohydrate"
case nfProtein = "nf_protein"
}
}
First : you make JSONFood while it should be foods
Second :food_name doesn't exist in current json root so this will fail
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
In case to take advantage of convertFromSnakeCase
let str = """
{"foods":[{"food_name":"Milk Chocolate","brand_name":"Snickers","serving_weight_grams":41.7,"nf_calories":212.3,"nf_total_fat":11.6,"nf_saturated_fat":4,"nf_total_carbohydrate":22.7,"nf_protein":3.9}]}
"""
do {
let res = JSONDecoder()
res.keyDecodingStrategy = .convertFromSnakeCase
let ss = try res.decode(Root.self, from:Data(str.utf8))
print(ss)
}
catch {
print(error)
}
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
}

How to properly decode nested JSON objects with Swift structs

Intent:
Receive cryptocurrency price data via Coinmarketcap API, decode it into custom structs in SWIFT and potentially store that data in a database (either CoreData or SQLite).
Context:
I am receiving the following error on JSONDecoder().decode:
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
Questions:
How to properly interpret that error? What am I decoding wrong?
Is the data I am receiving correctly formatted? Doesn't look like
proper JSON.
The code:
import UIKit
import PlaygroundSupport
// Defining structures
struct RootObject: Decodable {
let status: [String: StatusObject?]
let data: DataObject?
}
struct StatusObject: Decodable {
let credit_count: Int?
let elapsed: Int?
let error_code: Int?
let timestamp: String?
}
struct DataObject: Decodable {
let amount: Int?
let id: Int?
let last_updated: String?
let name: String?
let quote: [QuoteObject]?
let symbol: String?
}
struct QuoteObject: Decodable {
let usd: String?
}
struct usdObject: Decodable {
let last_updated: String?
let price: String?
}
//Configuring URLSession
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["X-CMC_PRO_API_KEY": "<removed>",
"Accept": "application/json",
"Accept-Encoding": "deflate, gzip"]
let session = URLSession(configuration: config)
let url = URL(string: "https://sandbox-api.coinmarketcap.com/v1/tools/price-conversion?convert=USD&amount=1&symbol=BTC")!
//Making and handling a request
let task = session.dataTask(with: url) { data, response, error in
guard error == nil else {
print ("error: \(error!)")
return
}
guard let content = data else {
print("No data")
return
}
//Serializing and displaying the received data
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any]
else {
print("Not containing JSON")
return
}
print(json)
//Trying to decode
do {
let prices = try JSONDecoder().decode(RootObject.self, from: data!)
print(prices)
} catch let decodeError {
print("Error serializing json:", decodeError)
}
}
task.resume()
The data response and the error:
["status": {
"credit_count" = 1;
elapsed = 6;
"error_code" = 0;
"error_message" = "<null>";
timestamp = "2019-02-16T11:10:22.147Z";
}, "data": {
amount = 1;
id = 1;
"last_updated" = "2018-12-22T06:08:23.000Z";
name = Bitcoin;
quote = {
USD = {
"last_updated" = "2018-12-22T06:08:23.000Z";
price = "3881.88864625";
};
};
symbol = BTC;
}]
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
Edit 1:
Properly serialized JSON:
{
"status": {
"timestamp": "2019-02-16T18:54:05.499Z",
"error_code": 0,
"error_message": null,
"elapsed": 6,
"credit_count": 1
},
"data": {
"id": 1,
"symbol": "BTC",
"name": "Bitcoin",
"amount": 1,
"last_updated": "2018-12-22T06:08:23.000Z",
"quote": {
"USD": {
"price": 3881.88864625,
"last_updated": "2018-12-22T06:08:23.000Z"
}
}
}
}
There are a lot of issues in the structs.
The main issue is that the value for data is a dictionary which is decoded into a struct rather than into another dictionary. Other issues are that the type of id is String and price is Double.
APIs like Coinmarketcap send reliable data so don't declare everything as optional. Remove the question marks.
The structs below are able to decode the JSON. The quotes are decoded into a dictionary because the keys change. Add the .convertFromSnakeCase key decoding strategy to get camelCased keys. The dates are decoded as Date by adding an appropriate date decoding strategy.
I removed all those redundant ...Object occurrences except DataObject because the Data struct already exists.
struct Root: Decodable {
let status: Status
let data: DataObject
}
struct Status: Decodable {
let creditCount: Int
let elapsed: Int
let errorCode: Int
let timestamp: Date
}
struct DataObject: Decodable {
let amount: Int
let id: String
let lastUpdated: Date
let name: String
let quote: [String:Quote]
let symbol: String
}
struct Quote: Decodable {
let lastUpdated: Date
let price: Double
}
//Trying to decode
do {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
let quotes = result.data.quote
for (symbol, quote) in quotes {
print(symbol, quote.price)
}
} catch {
print(error)
}