Parsing a nested JSON using Decodable - json

My JSON structure looks like this:
{
"code": 200,
"status": "Ok",
"etag": "7232324423",
"data": {
"offset": 0,
"limit": 25,
"results": [{
"id": 1011244,
"name": "Miss Nesbit",
"description": "",
"modified": "2018-04-04T20:15:35-0400",
"thumbnail": {
"path": "http://i.annihil.us/u/prod/i/mg/8/70/4c002efc322e3",
"extension": "jpg"
}
},
{
"id": 1011244,
"name": "Miss Solis",
"description": "",
"modified": "2018-09-04T20:15:35-0400",
"thumbnail": {
"path": "http://i.annihil.us/u/prod/i/mg/8/70/4c002efc382e3",
"extension": "jpg"
}
}
]
}
}
I want to parse the results in a struct as follows:
struct Character: Codable {
let id: Int
let name: String
let thumbnail: Thumbnail
let description: String
}
However I'm a bit confused about where I specify I only want the results part ? Would I do it when implementing Decodable as follows?
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)
Or do we have to map out each nested section? ANy help or guidance would be so appreciated! :)

Mapping out the relevant keys is necessary to drill down for you, yes.
You can use app.quicktype.io to get started fast and remove the non-relevant keys if you really don‘t want to parse the rest or leave it there if you may want to use it later.

You can use my extension helper NestedDecodable, which allows you to extract Decodable from a keyPath. It adds this method to JSONDecoder:
decoder.decode(Class, from: Data, keyPath: String)
A key path specifies the nested path to the model you want, separated by .
So using your example, this works (tested):
let characters = try decoder.decode([Character].self, from: data, keyPath: "data.results")
Internally, it creates a wrapper struct to hold your target Decodable class, split keyPath by ., then for-loop each key down until it reaches the final keyed decoding container, decode that model and returns. Ref

Related

Complex JSON in Swift. How to get the data correctly. Different structures

Faced a difficult problem for me, when receiving data I don’t know how to decompose data in one array.
The responsible variable contains different types of data.
Do I get it right? I think in the initializer to go through the possible options and substitute the desired one? What type should the variable of this array be?
[
{
"id": 42,
"created_at": "2021-09-08T08:55:58.000000Z",
"updated_at": "2021-09-08T08:55:58.000000Z",
"link": "u4986",
"type": "u",
"responsible": {
"id": 4986,
"type": "management_company",
"email": "X#X.com",
"phone": "+0000000000",
"comment": null,
"first_name": "Alex",
"second_name": "Hook"
}
},
{
"id": 43,
"created_at": "2021-09-08T08:55:58.000000Z",
"updated_at": "2021-09-08T08:55:58.000000Z",
"link": "r14",
"type": "r",
"responsible": {
"id": 14,
"name": "manager",
"guard_name": "api",
"created_at": "2021-06-15T19:20:20.000000Z",
"updated_at": "2021-06-15T19:20:20.000000Z"
}
}
]
How to make an initializer for MyJson
struct MyJson: Codable {
let id: Int
let createdAt: String
let updatedAt: String
let link: String
let type: String
let responsible: Any
}
// MARK: - Responsible
struct User: Codable {
let id: Int
let type, email, phone, comment: String
let firstName, secondName: String
}
struct UserCategory: Codable {
let id: Int
let name, guardName, createdAt, updatedAt: String
}
In swift JSON parsing is pretty straightforward, build an object that reflects your JSON (I've just built an example based on your JSON here):
struct JsonExample: Decodable {
let id: Int
let responsible: [Responsible]
struct Responsible: Decodable {
let id: Int
let email: String
let guard_name: String
}
}
and then just decode it
let jsonData = "json_string".data(using: .utf8)!
do {
let decoded = try JSONDecoder().decode([JsonExample].self, from: jsonData)
} catch let error {
print(error.localizedDescription)
}
If you want to distinguish between nested objects you can use init and use a property inside your JSON to get the job done
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
let type = try container.decode(String.self, forKey: .type)
if type == "a" {
let data = try container.decode([ResponsibleA].self, forKey: . responsible)
responsible = .responsibleA(data)
} else { // add better error handling
let data = try container.decode([ResponsibleB].self, forKey: . responsible)
responsible = .responsibleB(data)
}
}
Completed the task for a very long time. Finally I solved the problem. If you are facing this problem. You will find the solution here Indeterminate Types with Codable in Swift

Swift : How to create a dictionary dynamically from a json?

I'd like some advice from you. I would like to create a dictionary from a dynamic response fetch from an API and send that dictionary in an Alamofire POST request.
From what I have so far it's working but I'm not satisfied with what i've made and I think the code is really messy.
Here is an example of what I can receive
"content": {
"type": "form",
"fields": [
{
"type": "select",
"label": "Do you have your documents?",
"field": "user.has_docs",
"default": 0,
"choices": [
{
"value": 0,
"name": "Not yet"
},
{
"value": 1,
"name": "I do"
}
]
},
{
"type": "input",
"field": "user.date",
"label": "When do you arrive?",
}
]
}
After parsing the json with the Codable protocol, I have all my data in the Model Field
type: String
label: String
field: String
defaultValue: Int?
choice: [Choice]?
Choice
value: Int
name: String
So I want to create my dictionary and I want the following scheme :
{
"value": {
"user": {
"has_docs": 1,
"date": "29/07/2020"
}
}
}
The key named : "value" is always the same value, but the other one depends of the result from the API. the prefix of the field corresponding of "parent object" and the right part is the child.
Hard coding a dictionary in Swift is not that hard, I would do
let dict = [
"value": [
"user": [
"has_docs": 1,
"date": "29/07/2020"
]
]
]
But the troubles begin, at the attempt of creating a dictionary dynamically. Values inside user keep only the last one and replacing has_docs with date.
I have found a workaround with using flatmap and reduce but it only allows the type [String: String], unfortunately I need to write [String: Int] too in the dictionary.
here is a sample of the code
let flattenedDictionary = [key : dictionaries
.flatMap { $0 }
.reduce([String:String]()) { (dict, tuple) in
var nextDict = dict
nextDict.updateValue(tuple.1 as! String, forKey: tuple.0)
return nextDict
}]
parameters["value"] = flattenedDictionary
Here :
key = "user".
dictionaries = [["has_docs": 1], ["date": "29/07/2020"]]
Feel free to exchange if you need more informations
If you have any clue on how you could helping me, I'll highly appreciate, thanks for reading so far.
I hope I was very understandable.
Edit
From a general view : I'd like to create a dictionary dynamically
[String: [String: [String: Any]]]
A bit unclear if you have a [String: [String: [String: Any]]] or [String: [String: Any]] dictionary, but the concept of creating it dynamically would be rather similar.
var user: [String: Any] = [:]
user["has_docs"] = 1
user["date"] = Date()
let dict = ["value": user]

Swift 4 decode complex nested JSON

I'm trying to create a cleaner codable structure in which I can access the "description" by just typing day.description rather than day.weather.description
The description value is nested in an array "weather" which only contains a single object. I would like to extract the description from the index 0 and assign it to description in my struct.
Here is the JSON that I'm working with:
{
"dt": 1558321200,
"main": {
"temp": 11.88,
"temp_min": 11.88,
"temp_max": 11.88,
"pressure": 1013.3,
"sea_level": 1013.3,
"grnd_level": 1003.36,
"humidity": 77,
"temp_kf": 0
},
"weather": [{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01n"
}],
"clouds": {
"all": 0
},
"wind": {
"speed": 5.58,
"deg": 275.601
},
"sys": {
"pod": "n"
},
"dt_txt": "2019-05-20 03:00:00"
}
and the code I have so far:
struct Weather: Codable {
let days: [Day]
enum CodingKeys: String, CodingKey {
case days = "list"
}
}
struct Day: Codable {
let date: String
let main: Main
let wind: Wind
let description: String
enum CodingKeys: String, CodingKey {
case date = "dt_txt"
case main
case wind
case weather
case description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try container.decode(String.self, forKey: .date)
main = try container.decode(Main.self, forKey: .main)
wind = try container.decode(Wind.self, forKey: .wind)
let weather = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .weather)
description = try weather.decode(String.self, forKey: .description)
}
}
As you've been told, the simplest approach is just a computed property that references the desired value. However, for the sake of completeness we might as well discuss how to do the thing you actually asked to do. Let's illustrate with a reduced version of your JSON:
{
"dt": 1558321200,
"weather": [{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01n"
}]
}
So the question is, how can we parse this into, say, a struct Result with a description property such that we fetch out the "description" key from the first item in the "weather" array? Here's one approach:
struct Result : Decodable {
let description : String
enum Keys : CodingKey {
case weather
}
struct Weather : Decodable {
let description : String
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: Keys.self)
var arr = try! con.nestedUnkeyedContainer(forKey: .weather) // weather array
let oneWeather = try! arr.decode(Weather.self) // decode first element
self.description = oneWeather.description
}
}
Basically the idea here is that nestedUnkeyedContainer gives us our array, and subsequent calls to decode on that array automatically tackle each element in turn. We have only one element so we need only one decode call. How we dispose of the resulting string is up to us, so now we can slot it into our top-level description property.
But here's another approach. We don't even really need the secondary Weather struct; we can just dive right into the "weather" array and grab the first dictionary element and access its "description" key without saying any more about what's in that inner dictionary, like this:
struct Result : Decodable {
let description : String
enum Keys : CodingKey {
case weather
case description
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: Keys.self)
var arr = try! con.nestedUnkeyedContainer(forKey: .weather)
let con2 = try! arr.nestedContainer(keyedBy: Keys.self)
let desc = try! con2.decode(String.self, forKey: .description)
self.description = desc
}
}
Your question was not very complete (you didn't show your real JSON, just an excerpt), so I can't give any more precise advice, but I'm sure you can see how adapt this technique to your needs.

SWIFT 4 nested JSON Struct - Codable

I'm having issues creating a struct to parse JSON in Swift 4. I'm able to parse small JSONs and JSONDecoder seems to work fine. Just need help to create a struct to parse JSON like that:
{
"main": {
"solutions": [
{
"exersises": [
{
"book_title": "test",
"release_date": "2015-01-12T11:00",
"price": 100,
"additional": [
{
"item1": "test",
"item2": "test",
"number": 1
},
{
"item1": "test2",
"item2": "test2",
"number": 2
}
],
"availability": "Yes",
"item_id": 43534
}
]
}
]
}
}
What kind of struct do I need to get to value of book_title for example?
Its really easy. Your main probem is most likely root element. Let me get first layer or two for you.
let decoded = try JSONDecoder().decode(MainJSON.self, from: data)
class MainJSON: Codable {
var main:SolutionJSON?
}
class SolutionJSON: Codable {
var exercises:[ExercisesJSON]?
}
class ExercisesJSON: Codable {
var bookTitle: String?
var releaseDate: String?
var price: Double?
... etc
enum CodingKeys: String, CodingKey {
case bookTitle = "book_title"
case releaseDate = "release_date"
case price = "price"
}
}
ExerciseJSON also uses Codable interface which lets remap json properties into swift properties if they don't match. Hope this helps.
i prefer to give a general solution not only for this condition
it is very simple just download and run this MACOS APP from GITHUB
run it in your mac by XCODE and but your JSON in it,
it will make Models for any complex JSON
notes
1 if JSON keys have a capital character in the first it will be small
, so after copying model you need to change it like the JSON
2 if two JSON objects have the same structure and the same key names it will be only one model

Parsing JSON response in Swift 3

I've got an API endpoint that returns JSON in the following format:
[
{
"id": "1",
"name": "John"
},
{
"id": "2",
"name": "Jane"
},
{
"id": "3",
"name": "Nick"
}
]
I am trying to parse this in Swift 3, but I can only find examples to parse JSON formatted like so:
{
"blogs": [
{
"needspassword": true,
"id": 73,
"url": "http://remote.bloxus.com/",
"name": "Bloxus test"
},
{
"needspassword": false,
"id": 74,
"url": "http://flickrtest1.userland.com/",
"name": "Manila Test"
}
],
"stat": "ok"
}
, which has an extra level above what mine does.
So, where examples I've seen are simply parsing their data like jsonResponse["blogs"], I can't do that as my format is different.
How can I parse the format I've got, or how can I return a format that is easier to parse?
Any suggestions appreciated, thanks!
You can just do the following :
let data = // Data received from WS
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [[String : String]]
//json is now an array from dictionary matching your model
}
catch {
//handle error
}
This will parse it when placed in the network call.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [[String : AnyObject]]
let firstPerson = json[0]
print(firstPerson)
let id = firstPerson["id"] as! String
print(id)
let name = firstPerson["name"] as! String
print(name)
} catch {
//handle error
}
Also, I tend to be against advising third party libraries, but SwiftyJSON is an exception I make. If you want to try it, add this to your pod file:
pod SwiftyJSON', '3.0.0'
Documentation: https://github.com/SwiftyJSON/SwiftyJSON
EDIT - Answering Comment:
Replacement line:
if let id = firstPerson["id"] as? String {
print(id)
}
Replacement line (if you need to hold on to the value):
var thisId: String?
if let id = firstPerson["id"] as? String {
thisId = id
}
print(thisId ?? "")
i don't really know swift but there might be the equivalent of ajax json encoding (server side you json_encode your array and client side you json_decode the response)
the idea is to have the same formatter that encodes and decodes the data