Parsing nested JSON using Codable - json

So I'm trying to parse JSON that looks something like this using Codable in Swift.
{
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
],
"name": "SomeRandomName"
}
Now it gets confusing when you're trying to get nested data. Now I'm trying to get the name, which is easy. I'm also trying to get the ability name, this is where its gets complicated for me. After some research this is what I came up with.
class Pokemon: Codable {
struct Ability: Codable {
var isHidden: Bool
struct AbilityObject: Codable {
var name: String
var url: String
}
var ability: AbilityObject
private enum CodingKeys: String, CodingKey {
case isHidden = "is_hidden"
case ability
}
}
var name: String
var abilities: [Ability]
}
Now is there any better way in doing this, or am I stuck doing it like this.

Grab your JSON response and dump it in this site.
It'll generate these structs without Codable. Add Codable so they look like this:
struct Pokemon: Codable {
let abilities: [AbilityElement]
let name: String
struct AbilityElement: Codable {
let ability: Ability
let isHidden: Bool
let slot: Int
struct Ability: Codable {
let name: String
let url: String
}
}
}
For keys with snake_case, you can just declare a JSONDecoder and specify the keyDecodingStrategy as .convertFromSnakeCase. No need to muck around with coding keys if you're just converting from snake case. You only need them if you're renaming keys.
If you have other situations where you need to create custom coding keys for your responses or alter key names, this page should prove helpful.
You can dump this in a playground and play around with it:
let jsonResponse = """
{
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
],
"name": "SomeRandomName"
}
"""
struct Pokemon: Codable {
let abilities: [AbilityElement]
let name: String
struct AbilityElement: Codable {
let ability: Ability
let isHidden: Bool
let slot: Int
struct Ability: Codable {
let name: String
let url: String
}
}
}
var pokemon: Pokemon?
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
if let data = jsonResponse.data(using: .utf8) {
pokemon = try jsonDecoder.decode(Pokemon.self, from: data)
}
} catch {
print("Something went horribly wrong:", error.localizedDescription)
}
print(pokemon)

Related

How to decode in model when custom object's key missing from json

I have a Json in which there is the possibility that a few keys can be missing
Full JSON looks like this
{
"data": "some text",
"id": "3213",
"title": "title",
"description": "description",
"customObj1": [{
"id": "2423",
"count": 35
........
.......
}]
"customObj2": [{
"number": "2423",
"name": "john"
........
.......
}]
"customObj3": [{
"like": "2423",
"Other": 9
........
.......
}]
}
the custom object (1,2,3) may or may not be available in JSON, How can I write the model in swift using codable and struct?
here is how the model (dummy) look like
// MARK: - Response
struct Response: Codable {
let data, id, title, ResponseDescription: String
let customObj1: [CustomObj1]
let customObj2: [CustomObj2]
let customObj3: [CustomObj3]
enum CodingKeys: String, CodingKey {
case data, id, title
case ResponseDescription = "description"
case customObj1, customObj2, customObj3
}
}
// MARK: - CustomObj1
struct CustomObj1: Codable {
let id: String
let count: Int
}
// MARK: - CustomObj2
struct CustomObj2: Codable {
let number, name: String
}
// MARK: - CustomObj3
struct CustomObj3: Codable {
let like: String
let other: Int
enum CodingKeys: String, CodingKey {
case like
case other = "Other"
}
}
I have tried using decode init but need help
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if (values.contains(. customObj1)) {
// here I need help }
else {
self.category = nil
}
}
}

How to decode a nested JSON struct with Swift Decodable?

Here is my JSON
{
"myItems": {
"item1name": [{
"url": "google.com",
"isMobile": true,
"webIdString": "572392"
}, {
"url": "hulu.com",
"isMobile": false,
"webIdString": "sad1228"
}],
"item2name": [{
"url": "apple.com",
"isMobile": true,
"webIdString": "dsy38ds"
}, {
"url": "Facebook.com",
"isMobile": true,
"webIdString": "326dsigd1"
}, {
"url": "YouTube.com",
"isMobile": true,
"webIdString": "sd3dsg4k"
}]
}
}
The json can have multiple items (such as item1name, item2name, item3name,...) and each item can have multiple objects (the example has 2 objects for the first item and 3 objects for the second item).
Here is the structure I want it saved to (incomplete):
struct ServerResponse: Decodable {
let items: [MyItem]?
}
struct MyItem: Decodable {
let itemName: String?
let url: String?
let isMobile: Bool?
let webIdString: String?
}
In the example above, it would mean that the items list should have five MyItem objects. For example, these would be the MyItem objects:
#1:
itemName: item1name,
url: google.com,
isMobile: true,
webIdString: 572392
#2:
itemName: item1name,
url: hulu.com,
isMobile: false,
webIdString: sad1228
#3:
itemName: item2name,
url: apple.com,
isMobile: true,
webIdString: dsy38ds
#4:
itemName: item2name,
url: Facebook.com,
isMobile: true,
webIdString: 326dsigd1
#5:
itemName: item2name,
url: YouTube.com,
isMobile: true,
webIdString: sd3dsg4k
What would be the best way for me to do this using Decodable? Any help would be appreciated. I think this may be similar to this problem: How to decode a nested JSON struct with Swift Decodable protocol?
Use below code to decode nested JSON for your requirement:
import Foundation
// MARK: - Websites
struct Websites: Codable {
var myItems: MyItems
}
// MARK: - MyItems
struct MyItems: Codable {
var item1Name, item2Name: [ItemName]
enum CodingKeys: String, CodingKey {
case item1Name = "item1name"
case item2Name = "item2name"
}
}
// MARK: - ItemName
struct ItemName: Codable {
var url: String
var isMobile: Bool
var webIDString: String
enum CodingKeys: String, CodingKey {
case url, isMobile
case webIDString = "webIdString"
}
}
NOTE: change the name of the variables according to your need.
EDIT
If you're still facing issue, use this app to convert your JSON to Struct:
Quick Type
Here is the structure you are after:
struct Websites: Decodable {
let items: [Item]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
items = try container.decode([String: [Item]].self, forKey: .myItems).values.flatMap { $0 }
}
private enum Key: String, CodingKey {
case myItems
}
}
struct Item: Decodable {
let url: String
let isMobile: Bool
let webIdString: String
}

Array in JSON not parsing with custom decoding function in Swift

This question might seem similar to other questions with the same title. However, my decodable is in a function called load.
Here is what my JSON file looks like:
[
{
"id": 1001
"name": "tempName"
"item1": [
{
"id": 1101
"element": "tempElement"
},
{
"id": 1201
"element": "tempElement2"
},
]
}
]
I've built a struct temp to access the information within my JSON file after decoding it. I am not sure if it is extremely relevant but this is what it looks like:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: ExampleItem
}
struct ExampleItem: Hashable, Codable, Identifiable{
var id: Int
var element: String
}
My structs don't seem too crazy so I assume the problem occurs when I am parsing and decoding my JSON. This is the code I am using
let tempData: [Temp] = load("tempData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
I think Expected to decode Dictionary<String, Any> but found an array instead bug is occurring when the code tries to decode item1 because item1 is an array. What should I change in my load function?
First of all your JSON is invalid. It should be comma-separated:
[
{
"id": 1001,
"name": "tempName",
"item1": [
{
"id": 1101,
"element": "tempElement"
},
{
"id": 1201,
"element": "tempElement2"
},
]
}
]
Then you need to parse item1 as an array:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: [ExampleItem] // <- declare as an array
}
Note: I suggest renaming item1 to items. For this you need to use CodingKeys (it is as a custom mapping between a model and a JSON):
struct Temp: Hashable, Codable, Identifiable {
enum CodingKeys: String, CodingKey {
case id, name, items = "item1"
}
var id: Int
var name: String
var items: [ExampleItem]
}

Accessing nested object from JSON when it is a dynamic number represented as string

I'm accessing the data from an API with XCode(10.2.1) and Swift(5.0) and ran into a problem I cannot seem to find the answer to. I am able to get data from all other parts of the API apart from one, which has been named as a number string "750", im not sure how to grab that data when I can't use a variable that jsonDecoder can read?
This is an example of what I know won't work but gives you an idea of what I'm trying to do.
class Images: Codable {
let "750": String
init("750": String){
self."750" = "750"
}
}
Here's a snippet from the API I'm trying to get the images from:
"id": "069f7f26",
"sku": "AP",
"title": "Pizza",
"description": "A really great pizza",
"list_price": "9.95",
"is_vatable": true,
"is_for_sale": false,
"age_restricted": false,
"box_limit": 2,
"always_on_menu": false,
"volume": null,
"zone": null,
"created_at": "2017-03-06T10:52:43+00:00",
"attributes": [
{
"id": "670f0e7c",
"title": "Allergen",
"unit": null,
"value": "Products manufactured in a nut environment"
},
{
"id": "c29e7",
"title": "Weight",
"unit": "g",
"value": "300"
}
],
"tags": [
],
"images": {
"750": {
"src": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"url": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"width": 750
}
}
},
I setup an example that better match your situation in order to give you an overview on how to parse and access your JSON information dynamically with a Dictionary data type:
import Foundation
let jsonData = """
{
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
"""
let json = jsonData.data(using: .utf8)!
public struct Results: Codable {
public var images: [String:Image] = [:]
enum CodingKeys: String, CodingKey {
case images = "images"
}
}
public struct Image: Codable {
public var src: String = ""
public var url: String = ""
public var width: Int = 0
enum CodingKeys: String, CodingKey {
case src = "src"
case url = "url"
case width = "width"
}
}
if let results = try? JSONDecoder().decode(Results.self, from: json) {
let imagesDict = results.images
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value: \(value)")
}
}
If you try this snippet it will give you this output printed:
Key: 750
Value: Image(src: "https://some_website.co.uk/cms/product_image/some_image.jpg", url: "https://some_website.co.uk/cms/product_image/some_image.jpg", width: 750)
You can try out the snippet above online, if you copy paste it here and run it: http://online.swiftplayground.run/
### UPDATE (in response to comment)
In response to your comment, I found it easier to setup another example to show you how you can achieve that with your exact code sample that you shared in the comment itself.
I left everything as class and just added images in order to leave you an overview on how to achieve that.
In the end, I'd suggest to rename Products and Attributes into Product and Attribute. Also if there is no strong reason on why you choosed the model to be class, just change them to struct and as well if there is no strong reasons to keep most of the attributes of each model optional give them a default value as I did in the example above if you are always expecting some values/attributes to be there.
You can try and run this snippet as well in http://online.swiftplayground.run to try it out:
import Foundation
let jsonData = """
{
"data": [
{
"title": "titlex",
"description": "descx",
"list_price": "123,456",
"attributes": [
{
"title": "titlex",
"unit": "unitx",
"value": "valuex"
}
],
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
]
}
"""
let json = jsonData.data(using: .utf8)!
class AllProducts: Codable {
let data: [Products]
init(data: [Products]) {
self.data = data
}
}
class Products: Codable {
let title: String?
let description: String?
let list_price: String?
let attributes: [Attributes]?
let images: [String:Image]?
init(title: String, description: String, list_price: String, attributes: [Attributes], images: [String:Image]) {
self.title = title
self.description = description
self.list_price = list_price
self.attributes = attributes
self.images = images
}
}
class Attributes: Codable {
let title: String?
let unit: String?
let value: String?
init(title: String, unit: String, value: String) {
self.title = title
self.unit = unit
self.value = value
}
}
class Image: Codable {
let src: String?
let url: String?
let width: Int?
init(src: String, url: String, width: Int) {
self.src = src
self.url = url
self.width = width
}
}
// parsing/decoding
if let results = try? JSONDecoder().decode(AllProducts.self, from: json) {
if let imagesDict = results.data[0].images {
// there is an "images" for product at position 0 (the only one in my json example)
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value src: \(value.src)")
print("Value url: \(value.url)")
print("Value width: \(value.width)")
}
}
}
Output
Key: 750
Value src: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value url: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value width: Optional(750)

Parsing Array of dictionaries in Swift using Codable

I have a JSON response from an API and I cannot figure out how to convert it into an user object (a single user) using Swift Codable. This is the JSON (with some elements removed for ease of reading):
{
"user": [
{
"key": "id",
"value": "093"
},
{
"key": "name",
"value": "DEV"
},
{
"key": "city",
"value": "LG"
},
{
"key": "country",
"value": "IN"
},
{
"key": "group",
"value": "OPRR"
}
]
}
You could do it in two steps if you want to. First declare a struct for the received json
struct KeyValue: Decodable {
let key: String
let value: String
}
Then decode the json and map the result into a dictionary using the key/value pairs.
do {
let result = try JSONDecoder().decode([String: [KeyValue]].self, from: data)
if let array = result["user"] {
let dict = array.reduce(into: [:]) { $0[$1.key] = $1.value}
Then encode this dictionary into json and back again using a struct for User
struct User: Decodable {
let id: String
let name: String
let group: String
let city: String
let country: String
}
let userData = try JSONEncoder().encode(dict)
let user = try JSONDecoder().decode(User.self, from: userData)
The whole code block then becomes
do {
let decoder = JSONDecoder()
let result = try decoder.decode([String: [KeyValue]].self, from: data)
if let array = result["user"] {
let dict = array.reduce(into: [:]) { $0[$1.key] = $1.value}
let userData = try JSONEncoder().encode(dict)
let user = try decoder.decode(User.self, from: userData)
//...
}
} catch {
print(error)
}
A bit cumbersome but no manual key/property matching is needed.
you could try a struct with like this
struct Model: Codable {
struct User: Codable {
let key: String
let value: String
}
let singleuser: [User]
}
Create a struct with 2 variables key and value
public struct UserModel: Codable {
public struct User: Codable {
public let key: String
public let value: String
}
public let user: [User]
}
After that using JSONDecoder to decode your string.
func decode(payload: String) {
do {
let template = try JSONDecoder().decode(UserModel.self, from: payload.data(using: .utf8)!)
} catch {
print(error)
}
}