Decode Custom Json with Decodable - json

I have this Json:
{ "first": {
"house": [
"small"
]
}, "second": {
"house": [
"small"
] }, "third": {
"car": [
"fast",
"economic"
] }, "fourth": {
"car": [
"fast",
"economic"
] }, "fifth": {
"car": [
"fast",
"economic"
],
"ice": [
"round",
"tasty"
],
"tree": [
"big",
"small"
] } }
I tried to set up a structure with Decodable but I do not get it to work:
struct secondLayer: Codable {
let exchange: [String: [String]]
}
struct decodeJson: Codable {
let symbol: [String: [secondLayer]]
static func decode(jsonString: String) - [decodeJson] {
var output = [decodeJson]()
let decode = JSONDecoder()
do {
let json = jsonString.data(using: .utf8)
output = try! decode.decode([decodeJson].self, from: json!)
} catch {
print(error.localizedDescription)
}
return output
}
}
I get this Error:
Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.typeMismatch(Swift.Array<Any>,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Array<Any but found a dictionary instead.",
underlyingError: nil)): file
/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.74.1/src/swift/stdlib/public/core/ErrorType.swift,
line 181
I tried some modification but I do not get it to work.

The error message
"Expected to decode Array<Any> but found a dictionary instead."
is very clear. You want to decode an array ([decodeJson]) but the root object is a dictionary (starting with {)
Your code cannot work anyway. There are no keys exchange and symbol in the JSON.
Basically there are two ways to decode that JSON:
If all keys are dynamic you cannot decode the JSON to structs. You have to decode it to [String:[String:[String]]]. In this case Codable has no benefit over traditional JSONSerialization.
struct DecodeJson: Codable {
static func decode(jsonString: String) -> [String:[String:[String]]] {
var output = [String:[String:[String]]]()
let decoder = JSONDecoder()
do {
let json = Data(jsonString.utf8)
output = try decoder.decode([String:[String:[String]]].self, from: json)
print(output)
} catch {
print(error.localizedDescription)
}
return output
}
}
Or if the ordinal keys first, second etc are static use an umbrella struct
struct Root : Codable {
let first : [String:[String]]
let second : [String:[String]]
let third : [String:[String]]
let fourth : [String:[String]]
let fifth : [String:[String]]
}
struct DecodeJson {
static func decode(jsonString: String) -> Root? {
let decoder = JSONDecoder()
do {
let json = Data(jsonString.utf8)
let output = try decoder.decode(Root.self, from: json)
return output
} catch {
print(error.localizedDescription)
return nil
}
}
}
Of course you can decode house, car etc into a struct but this requires a custom initializer for each struct because you have to decode a single array manually with unkeyedContainer

Related

Decoder not decoding json at keypath

Im trying to decode some JSON, but it's not parsing it. I think it may have something to to with either an incorrect KeyPath or the object itself. But I cannot figure it out.
This is the JSON that I want to decode (I want the array inside the docs path):
{
"status": 200,
"data": {
"docs": [
{
"_id": "60418a6ce349d03b9ae0669e",
"title": "Note title",
"date": "2015-03-25T00:00:00.000Z",
"body": "this is the body of my note.....",
"userEmail": "myemail#gmail.com"
}
],
"total": 1,
"limit": 20,
"page": 1,
"pages": 1
},
"message": "Notes succesfully Recieved"
}
Here's my decode function:
extension JSONDecoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data, keyPath: String) throws -> T {
let toplevel = try JSONSerialization.jsonObject(with: data)
if let nestedJson = (toplevel as AnyObject).value(forKeyPath: keyPath) {
let nestedJsonData = try JSONSerialization.data(withJSONObject: nestedJson)
return try decode(type, from: nestedJsonData)
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Nested json not found for key path \"\(keyPath)\""))
}
}
}
And i'm calling it like this:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let notes = try decoder.decode([Note].self, from: data, keyPath: "data.docs")
Finally, this is my Note Struct:
struct Note: Codable {
var title: String?
let date: Date?
var body: String?
let userEmail: String?
}
The problem was that I was trying to decode date as a Date object instead of a String as is shown on the JSON.
Thanks #vadian!

How to serialize JSON string to multidimensional NSDictionary

"[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},
{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},
{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
Note that the value "data" is also a dictionary.
I have a JSON string like above and am trying to serialize like:
if let dataFromString = conf.data(using: .utf8, allowLossyConversion: false) {
let json = try JSON(data: dataFromString)
configuration = json.dictionary ?? [:]
}
However configuration is always an empty dictionary.
You need to parse the JSON you've as an array of dictionaries of type [[String: Any]]. The better modern approach is to use Decodable model to decode the JSON.
let string = """
[
{
"person": "person1",
"data": {
"age": "10",
"name": "John"
}
},
{
"person": "person2",
"data": {
"age": "20",
"name": "Jonathan"
}
},
{
"person": "person3",
"data": {
"age": "30",
"name": "Joe"
}
}
]
"""
let data = Data(string.utf8)
struct Person: Decodable {
let person: String
let data: PersonData
}
struct PersonData: Decodable {
let age, name: String
}
do {
let people = try JSONDecoder().decode([Person].self, from: data)
print(people)
} catch { print(error) }
For the JSON String,
let conf = "[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
use JSONSerialization's jsonObject(with:options:) method to get the expected response.
if let conf = str.data(using: .utf8 ) {
do {
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [[String:Any]]
print(dict)
} catch {
print(error)
}
}

How to Parse Json dictonary in swift 4

Hi I have a problem with this Json:
{
"id": "libMovies",
"jsonrpc": "2.0",
"result": {
"limits": {
"end": 75,
"start": 0,
"total": 1228
},
"movies": [{
"art": {
"fanart": "myfanart",
"poster": "myposter"
},
"file": "myfile",
"label": "mylable",
"movieid": mymovieid,
"playcount": 0,
"rating": myrating,
"thumbnail": "mythumbnail"
}]
}
}
When I parse Json in swift 5 with this code
try! JSONDecoder().decode([MyMovie].self, from: data!)
I get this error
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil)):
How can I solve this?
For the below JSON,
{"id":"libMovies","jsonrpc":"2.0","result":{"limits":{"end":75,"start":0,"total":1228},"movies":[{"art":{"fanart":"myfanart","poster":"myposter"},"file":"myfile","label":"mylable","movieid":"mymovieid","playcount":0,"rating":"myrating","thumbnail":"mythumbnail"}]}}
The Codable models that you need to use,
struct Root: Decodable {
let id, jsonrpc: String
let result: Result
}
struct Result: Decodable {
let limits: Limits
let movies: [Movie]
}
struct Limits: Decodable {
let end, start, total: Int
}
struct Movie: Decodable {
let art: Art
let file, label, movieid: String
let playcount: Int
let rating, thumbnail: String
}
struct Art: Decodable {
let fanart, poster: String
}
Parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response.result.movies.map({"file: \($0.file), label: \($0.label)"}))
} catch {
print(error)
}
Edit:
To save the movies separately, create a variable of type [Movie],
var movies = [Movie]()
Now, while parsing save the response.result.movies in the above created property,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response.result.movies.map({"file: \($0.file), label: \($0.label)"}))
movies = response.result.movies
} catch {
print(error)
}

Decoding a nested JSON Swift

I have to decode this type of JSON that is downloaded by a response.
The JSON is this, I need retrieve the "gallery" of all items
JSON: https://pastebin.com/KnEwZzxd
I have tried many solution but I am not able to create a Decode of this son.
I have posted the full code on pastebin, too.
{
"status": 200,
"data": {
"date": "2018-07-29T00:00:00.300Z",
"featured": [
{
"id": "5b56298d781e197186378f50",
"name": "Sun Tan Specialist",
"price": "1,500",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/icon.png",
"png": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/png.png",
"gallery": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/gallery.jpg",
"featured": "https://image.fnbr.co/outfit/5b56298d781e197186378f50/featured.png"
},
"rarity": "epic",
"type": "outfit",
"readableType": "Outfit"
},
{
"id": "5b562af2781e19db65378f5c",
"name": "Rescue Paddle",
"price": "800",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/icon.png",
"png": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/png.png",
"gallery": "https://image.fnbr.co/pickaxe/5b562af2781e19db65378f5c/gallery.jpg",
"featured": false
},
"rarity": "rare",
"type": "pickaxe",
"readableType": "Pickaxe"
}
],
"daily": [
{
"id": "5ab1723e5f957f27504aa502",
"name": "Rusty Rider",
"price": "1,200",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/icon.png",
"png": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/png.png",
"gallery": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/gallery.jpg",
"featured": "https://image.fnbr.co/glider/5ab1723e5f957f27504aa502/featured.png"
},
"rarity": "epic",
"type": "glider",
"readableType": "Glider"
},
{
"id": "5b0e944bdb94f1a4bbc0a8e4",
"name": "Rambunctious",
"price": "500",
"priceIcon": "vbucks",
"priceIconLink": "https://image.fnbr.co/price/icon_vbucks.png",
"images": {
"icon": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/icon.png",
"png": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/png.png",
"gallery": "https://image.fnbr.co/emote/5b0e944bdb94f1a4bbc0a8e4/gallery.jpg",
"featured": false
}
]
}
}
Aside from posting the JSON Code itself, it would be useful to actually show an attempt as to how you have attempted to decode it as well ^_________^.
Anyway, the best way to tackle this issue is to use custom Structs and the Decodable Protocol to handle the JSON response.
From your JSON you will initially get a two values:
/// The Initial Response From The Server
struct Response: Decodable {
let status: Int
let data: ResponseData
}
From this we then map the 'data' to a struct called ResponseData:
/// The Data Object
struct ResponseData: Decodable{
let date: String
let featured: [Product]
let daily: [Product]
}
In this we have two variables which contain an array of identical struct which I have called Product:
/// The Product Structure
struct Product: Decodable{
let id: String
let name: String
let price: String
let priceIcon: String
let priceIconLink: String
let images: ProductImages
let rarity: String
let type: String
let readableType: String
}
Within this we have one variable which is a dictionary (images) which we then map to another struct:
/// The Data From The Product Images Dictionary
struct ProductImages: Decodable{
let icon: String
let png: String
let gallery: String
///The Featured Variable For The Product Images Can Contain Either A String Or A Boolean Value
let featured: FeaturedType
}
The issue you have with the ProductImages, is that the featured var sometimes contains a String but on others it contains a Bool value. As such we need to create a custom struct to handle the decoding of this to ensure we always get a String (I am probably not doing this right way so if someone has a better solution please say so):
/// Featured Type Returns A String Of Either The Boolean Value Or The Link To The JPG
struct FeaturedType : Codable {
let formatted: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
//1. If We Get A Standard Response We Have A String
let stringResult = try container.decode(String.self)
formatted = stringResult
} catch {
//2. On Occassions We Get An Bool
let boolResult = try container.decode(Bool.self)
formatted = String(boolResult)
}
}
}
Now that is the basic structure of your JSON so now you need to handle it. In this example I am loading the JSON from the MainBundle as I dont have the actual URL.
/// Loads & Decodes The JSON File
func retreiveJSON(){
//1. Load The JSON File From The Main Bundle
guard let jsonURL = Bundle.main.url(forResource: "sample", withExtension: ".json") else { return }
do{
//2. Get The Data From The URL
let data = try Data(contentsOf: jsonURL)
//3. Decode The JSON
let jsonData = try JSONDecoder().decode(Response.self, from: data)
//4. Extract The Data
extractDataFrom(jsonData)
}catch{
print("Error Processing JSON == \(error)")
}
}
In the above function you will notice I am calling the function extractDataFrom() which allows you to then do what you need to do with your data:
/// Extracts The Daily & Featured Products From The JSON
///
/// - Parameter jsonData: Response
func extractDataFrom(_ jsonData: Response){
//1. Get The Daily Products
let dailyProducts = jsonData.data.daily
dailyProducts.forEach { (product) in
print(product.id)
print(product.name)
print(product.price)
print(product.priceIcon)
print(product.priceIconLink)
print(product.images)
print(product.rarity)
print(product.type)
print(product.readableType)
}
//2. Get The Featured Products
let featuredProducts = jsonData.data.featured
featuredProducts.forEach { (product) in
print(product.id)
print(product.name)
print(product.price)
print(product.priceIcon)
print(product.priceIconLink)
print(product.images)
print(product.rarity)
print(product.type)
print(product.readableType)
}
}
If you wanted to save this data then all you would need to do is add the following variables under your class declaration e.g:
var featuredProducts = [Product]()
var dailyProducts = [Product]()
And in the extractDataFrom() function change the:
let dailyProducts
let featuredProducts
To:
dailyProducts = jsonData.data.daily
featuredProducts = jsonData.data.featured
Please note that this is a very crude example, and as noted, I may not be handling the 'featured' variable correctly.
Hope it helps...
Thanks to quicktype and other services converting valid json to Swift and other languages is simple. Editing it to fit your needs should be simple enough.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
struct Welcome: Codable {
let status: Int
let data: DataClass
}
struct DataClass: Codable {
let date: String
let featured, daily: [Daily]
}
struct Daily: Codable {
let id, name, price, priceIcon: String
let priceIconLink: String
let images: Images
let rarity, type, readableType: String?
}
struct Images: Codable {
let icon, png, gallery: String
let featured: Featured
}
enum Featured: Codable {
case bool(Bool)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Featured.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Featured"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}

JSON Decoding Error - typeMissmatch Swift 4

I'm trying to parse some data from JSON, I already got that working with another API but now I have another struct and I'm getting typeMissmatch Erros...
The JSON looks like:
{
"status": 200,
"data": {
"date": "2018-04-07T00:00:00.508Z",
"featured": [
{
"id": "2345",
"name": "name",
"price": "1,000",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
],
"daily": [
{
"id": "12324",
"name": "name",
"price": "1,500",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
]
}}
And a Codable struct like that:
struct Base : Codable {
let status : Int
let data : DataItems
}
struct DataItems : Codable {
let date : String
let featured : [Featured]
let daily : [Daily]
}
struct Featured : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Daily : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Images : Codable {
let icon : String
let png : String
let gallery : String
let featured : String
}
But when I try to decode that Json I get a "Swift.DecodingError.typeMismatch" Error:
▿ Swift.DecodingError.typeMismatch
▿ typeMismatch: (2 elements)
- .0: Swift.String #0
▿ .1: Swift.DecodingError.Context
▿ codingPath: 5 elements
- CodingKeys(stringValue: "data", intValue: nil)
- CodingKeys(stringValue: "daily", intValue: nil)
▿ _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue: "Index 0"
▿ intValue: Optional(0)
- some: 0
- CodingKeys(stringValue: "images", intValue: nil)
- CodingKeys(stringValue: "featured", intValue: nil)
- debugDescription: "Expected to decode String but found a number instead."
- underlyingError: nil
My JSON Decoder:
enum Result<Value> {
case success(Value)
case failure(Error)
}
func getItems(for userId: Int, completion: ((Result<Base>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.jsonbin.io"
urlComponents.path = "/myurl"
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"secret-key": "xyz"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonDataTest = responseData {
// Now we have jsonData, Data representation of the JSON returned to us
// from our URLRequest...
// Create an instance of JSONDecoder to decode the JSON data to our
// Codable struct
let decoder = JSONDecoder()
do {
// We would use Post.self for JSON representing a single Post
// object, and [Post].self for JSON representing an array of
// Post objects
let posts = try decoder.decode(Base.self, from: jsonDataTest)
completion?(.success(posts))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
var base:Base?
func loadJson() {
getItems(for: 1) { (result) in
switch result {
case .success(let base):
self.base = base
dump(base)
case .failure(let error):
fatalError("error: \(error.localizedDescription)")
}
}
}
I'm new to swift and not sure what this Error is telling me or where the problem "decode String but found a number" is. I think there is something wrong with me struct.. I hope someone can help me there.
Please show the code where you want to parse the data to json.
let urlString = "your_url.json"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let baseData = try JSONDecoder().decode(Base.self, from: data)
print(baseData) //whole project
print(baseData.status) //200.0
print(baseData.data.date)
for day in baseData.data.daily {
print(day.id)
print(day.images.icon)
print(day.images.featured)
print(day.images.gallery)
print(day.images.png)
print(day.name)
print(day.price)
print(day.priceIcon)
print(day.priceIconLink)
print(day.rarity)
print(day.readableType)
print(day.type)
}
for feature in baseData.data.featured {
print(feature.id)
print(feature.images.icon)
print(feature.images.featured)
print(feature.images.gallery)
print(feature.images.png)
print(feature.name)
print(feature.price)
print(feature.priceIcon)
print(feature.priceIconLink)
print(feature.rarity)
print(feature.readableType)
print(feature.type)
}
} catch let jsonError {
print(jsonError)
}
}.resume()
I tried this and it works for me.
By the way I was a little bit confused that Featured and Daily have all the same variables but are different models.
EDIT
Your posted data in the question are valid. The json from https://api.jsonbin.io/b/5acbd2dc214f9a2b84c6f167/1 is wrong or not consistent.
There is "featured": false in Daily and in Featured it is a string. In the struct is a string expected. So you will get a mismatch. Once you try to parse a string (works) and then you try to parse a boolean to a string (error).