Getting key from Dictionary into neasted Decodable model - swift

Let's say we have a JSON like that:
{
"id1": {
"name": "hello"
},
"id2": {
"name": "world"
}
}
A model:
struct Model: Decodable {
var id: String
var name: String
}
How is it possible to make an array of Model from the JSON above?

You could do something like this
let data = """
{
"id1": {
"name": "hello"
},
"id2": {
"name": "world"
}
}
""".data(using: .utf8)!
struct Name: Decodable {
let name: String
}
struct Model {
let id: String
let name: String
}
do {
let json = try JSONDecoder().decode([String: Name].self, from: data)
let result = json.map { Model(id: $0.key, name: $0.value.name) }
print(result)
} catch {
print(error)
}
We decode the data as [String, Name]. We could decode it as [String: [String:String]] but this will mean that we will have to handle optional values so it is easier to create a Name struct to handle that part.
Once we have the dictionary we map over it converting it into the model object, leaving an array of [Model]

Related

How to define a struct which can may or may not have fixed fields

This is the struct I am using and need your help in finding what will be the value of
data field.
struct ResponceData: Codable {
let dataRes: [DataResStruct]
struct DataResStruct: Codable {
let data: <what should be used>
let key: String
}
}
My responce data could be is like below:
Resp 1
{
"dataRes": [{
"data": [{
"key1_d": "val1_v",
"key2_d": "val2_v",
"key3_d": "val3_v"
}],
"key": "1"
}]
}
Resp 2
{
"dataRes": [{
"data": [{
"key1_A": "val1_B",
"key2_A": "val2_B",
"key3_A": "val3_B",
"key4_A": "val4_B",
"key5_A": "val5_B"
},
{
"key1_C": "val1_D",
"key1_C": "val1_D"
}
],
"key": "2"
}]
}
As described, this type is:
struct ResponceData: Codable {
let dataRes: [DataResStruct]
struct DataResStruct: Codable {
let data: [[String:String]]
let key: String
}
}
Whether that's really the correct type, or if it is particularly useful, depends on how close the actual data you're parsing is to what you've posted here.

Swift Codable: decode dictionary with unknown keys

Codable is great when you know the key formatting of the JSON data. But what if you don't know the keys? I'm currently faced with this problem.
Normally I would expect JSON data to be returned like this:
{
"id": "<123>",
"data": [
{
"id": "<id1>",
"event": "<event_type>",
"date": "<date>"
},
{
"id": "<id2>",
"event": "<event_type>",
"date": "<date>"
},
]
}
But this is what I'm aiming to decode:
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "<date>" } },
{ "<id2>": { "<event>": "<date>" } },
]
}
Question is: how do I use Codable to decode JSON where the keys are unique? I feel like I'm missing something obvious.
This is what I'm hoping to do so I can use Codable:
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Any]]]
// MARK: - Decoding
enum CodingKeys: String, CodingKey {
case id = "id"
case data = "data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
// This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
data = try container.decode([[String: [String: Any]]].self, forKey: .data)
}
}
This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
For your completely changed question, the solution is very similar. Your struct simply adds one additional layer above the array. There's no need for any custom decoding nor even any CodingKeys.
Note that you can't use Any in a Codable.
let json="""
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "2019-05-21T16:15:34-0400" } },
{ "<id2>": { "<event>": "2019-07-01T12:15:34-0400" } },
]
}
"""
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Date]]]
}
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode(SampleModel.self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}
The original answer for your original question.
Since you have an array of nested dictionary where none of the dictionary keys are fixed, and since there are no other fields, you can just decode this as a plain array.
Here's an example:
let json="""
[
{ "<id1>": { "<event>": "2019-07-01T12:15:34-0400" } },
{ "<id2>": { "<event>": "2019-05-21T17:15:34-0400" } },
]
"""
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode([[String: [String: Date]]].self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}

Swift: Help For Json parsing codeable/decodable

I am new to iOS, and want to parse the JSON using Decodable but cant get through this, how should I work this out?
The view controller where I am trying to parse the data
class ViewController: UIViewController {
var servers = [Server]()
let apiUrl = "https://someurl/api/dashboard"
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: self.apiUrl) else { return }
getDataFrom(url)
}
fileprivate func getDataFrom(_ url: URL) {
URLSession.shared.dataTask(with: url){ (data, response, error) in
guard let data = data else { return }
do {
let apiResponse = try JSONDecoder().decode(Server.self, from: data)
print(apiResponse)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
The Server.swift file where I am confirming to the decodable protocol
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customer: [Customer]
let latest_value: [LatestValue]
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
No value associated with key CodingKeys I get this error,
The response from the server
{
"servers": {
"current_page": 1,
"data": [
{
"hostname": "johndoes",
"ipaddress": "10.0.2.99",
"id": 7,
"latest_value_id": 1130238,
"customers": [
{
"name": "Jane Doe",
"contact_person": "John Doe",
"id": 2,
"email": "john.#example.com",
"pivot": {
"server_id": 7,
"customer_id": 2
}
}
],
"latest_value": {
"id": 1130238,
"server_id": 7,
"systemuptime": "80days:10hours:23minutes",
"memtotal": 3.7,
"memfree": 1.6400000000000001,
"loadaverage": 2.25,
"disktotal": {
"dev-mapper-centos-root_disktotal": "38",
"dev-mapper-proquote-xfs-lvm_disktotal": "200"
},
"diskused": "{\"dev-mapper-centos-root_diskused\":\"16\",\"dev-mapper-proquote-xfs-lvm_diskused\":\"188\"}",
"custom_field": "[]",
"additional_attributes": {
"fathom": {
"name": "fathom",
"status": 1
},
"trenddb": {
"name": "trenddb",
"status": 1
},
"trendwi": {
"name": "trendwi",
"status": 1
},
"appsrv": {
"name": "appsrv",
"status": 1
}
},
"created_at": "2019-06-15 02:25:02",
"updated_at": "2019-06-15 02:25:02"
}
}
]
},
"message": "Success"
}
You seem to have few different errors in your data structure.
First of all, you are trying to decode Server while your json has servers inside a dict {"servers": ... }, So use a parent root object for it.
Your latest_value inside ServerData is defined as array, while it should be LatestValue struct not [LatestValue].
There is no first_page_url element in your json, but your Server struct has the property, make it optional, so that JSONDecoder decodes it only if it is present.
Here is your refined data models.
struct Response: Decodable {
let servers: Server
}
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String?
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customers: [Customer]
let latest_value: LatestValue
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
And decode Response instead of decoding Server, like so,
do {
let apiResponse = try JSONDecoder().decode(Response.self, from: data)
let server = apiResponse.server // Here is your server struct.
print(server)
} catch let jsonError {
print(jsonError)
}

Swift 4 decoding json using Codable

Can someone tell me what I'm doing wrong? I've looked at all the questions on here like from here How to decode a nested JSON struct with Swift Decodable protocol? and I've found one that seems exactly what I need Swift 4 Codable decoding json.
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
struct Location: Codable {
var data: [LocationList]
}
struct LocationList: Codable {
var LocID: Int!
var LocName: String!
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "/getlocationlist")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
}
task.resume()
}
The error I am getting is:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[], debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
Check the outlined structure of your JSON text:
{
"success": true,
"message": "got the locations!",
"data": {
...
}
}
The value for "data" is a JSON object {...}, it is not an array.
And the structure of the object:
{
"LocationList": [
...
]
}
The object has a single entry "LocationList": [...] and its value is an array [...].
You may need one more struct:
struct Location: Codable {
var data: LocationData
}
struct LocationData: Codable {
var LocationList: [LocationItem]
}
struct LocationItem: Codable {
var LocID: Int!
var LocName: String!
}
For testing...
var jsonText = """
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
"""
let data = jsonText.data(using: .utf8)!
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
After searching lots of thing internet, I certainly figured out this is the sweetest way to print well formatted json from any object.
let jsonString = object.toJSONString(prettyPrint: true)
print(jsonString as AnyObject)
Apple documentation about JSONEncoder ->
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/

Swift 4 Codable decoding json

I'm trying to implement the new Codable protocol, so I added Codable to my struct, but am stuck on decoding the JSON.
Here's what I had before:
Struct -
struct Question {
var title: String
var answer: Int
var question: Int
}
Client -
...
guard let data = data else {
return
}
do {
self.jsonResponse = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
let questionItems = self.jsonResponse?["themes"] as! [[String: Any]]
questionItems.forEach {
let item = Question(title: $0["title"] as! String,
answer: $0["answer"] as! Int,
question: $0["question"] as! Int)
questionData.append(item)
}
} catch {
print("error")
}
Here's what I have now, except I can't figure out the decoder part:
Struct -
struct Question: Codable {
var title: String
var answer: Int
var question: Int
}
Client -
...
let decoder = JSONDecoder()
if let questions = try? decoder.decode([Question].self, from: data) {
// Can't get past this part
} else {
print("Not working")
}
It prints "Not working" because I can't get past the decoder.decode part. Any ideas? Will post any extra code as needed, thanks!
EDIT:
Sample of API JSON:
{
"themes": [
{
"answer": 1,
"question": 44438222,
"title": "How many letters are in the alphabet?"
},
{
"answer": 0,
"question": 44438489,
"title": "This is a random question"
}
]
}
If I print self.jsonResponse I get this:
Optional(["themes": <__NSArrayI 0x6180002478f0>(
{
"answer" = 7;
"question" = 7674790;
title = "This is the title of the question";
},
{
"answer_" = 2;
"question" = 23915741;
title = "This is the title of the question";
}
My new code:
struct Theme: Codable {
var themes : [Question]
}
struct Question: Codable {
var title: String
var answer: Int
var question: Int
}
...
if let decoded = try? JSONDecoder().decode(Theme.self, from: data) {
print("decoded:", decoded)
} else {
print("Not working")
}
If your JSON has a structure
{"themes" : [{"title": "Foo", "answer": 1, "question": 2},
{"title": "Bar", "answer": 3, "question": 4}]}
you need an equivalent for the themes object. Add this struct
struct Theme : Codable {
var themes : [Question]
}
Now you can decode the JSON:
if let decoded = try? JSONDecoder().decode(Theme.self, from: data) {
print("decoded:", decoded)
} else {
print("Not working")
}
The containing Question objects are decoded implicitly.
You're getting this error because your JSON is likely structured as so:
{
"themes": [
{ "title": ..., "question": ..., "answer": ... },
{ "title": ..., "question": ..., "answer": ... },
{ ... }
],
...
}
However, the code you wrote expects a [Question] at the top level. What you need is a different top-level type that has a themes property which is a [Question]. When you decode that top-level type, your [Question] will be decoded for the themes key.
Hello #all I have added the code for JSON Encoding and Decoding for Swift 4.
Please use the link here