Swift 4 decodable nested json with random key attributes - json

I'm having problems decoding json. I've followed lots of tutorials but non use complex json structures. For simplicity I minimized the code and use Dog as example.
In following json i'm mostly only interested in the Dog structs. The json "Data" attribute contains random dog names. So I cannot use coding keys because I dont know the attribute name.
{
"Response": "success"
"BaseLinkUrl": "https://wwww.example.com",
"Data": {
"Max": {
"name": "Max",
"breed": "Labrador"
},
"Rocky": {
"name": "Rocky",
"breed": "Labrador"
},
...
}
}
I have following structs:
struct DogResponse : Decodable {
let data : DogResponseData
enum CodingKeys: String, CodingKey {
case data = "Data"
}
}
struct DogResponseData: Decodable {
let dog: Dog //this is a random variable name
enum CodingKeys: String, CodingKey {
case dog = "??random_variable_dog_name??"
}
}
struct Dog: Decodable {
let name: String
let type: String
enum CodingKeys: String, CodingKey {
case name
case type = "breed"
}
}
collecting the Dog structs:
let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse)
What do I need to do in my "DogResponseData" struct for swift to recognize a random variable that contains my Dog struct?

A possible solution is to write a custom initializer to decode the dictionaries as [String:Dog] and map the values to an array
struct Dog : Decodable {
let name : String
let breed : String
}
struct DogResponse : Decodable {
let dogs : [Dog]
private enum CodingKeys: String, CodingKey {
case data = "Data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let data = try values.decode([String : Dog].self, forKey: .data)
dogs = Array(data.values)
}
}
let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse.dogs)
===========================================================================
Or if you want to keep the dictionary structure it's still shorter
struct Dog : Decodable {
let name : String
let breed : String
}
struct DogResponse : Decodable {
let dogs : [String : Dog]
private enum CodingKeys: String, CodingKey {
case dogs = "Data"
}
}

It's worth keeping in mind that CodingKey is a protocol, not necessarily an enum. So you can just make it a struct and it will accept any random string value you throw at it.

Related

How to Parse Nested part of a JSON based on a condition in Swift

Other suggested solutions handle type of structure they are part of, While I am not able to figure out how to parse nested part of that same structure based on the value within outer structure.
I have a JSON response whose structure change based on one of the values within outer part of JSON.
For example:
{
"reports": [
{
"reportType": "advance",
"reportData": {
"value1": "true"
}
},
{
"reportType": "simple",
"reportData": {
"value3": "false",
"value": "sample"
}
}
]
}
Using Codable with string as Type for 'report' key fails to parse this json.
I want this report value to be either parsed later and store it as it is or atleast parse it based on the reportType value as this have different structure for each value of reportType.
I have written code based on the the suggested solutions.
enum ReportTypes: String {
case simple, advance
}
struct Reports: Codable {
let reportArray = [Report]
}
struct Report: Decodable {
let reportType: String
let reportData: ReportTypes
enum CodingKeys: String, CodingKey {
case reportType, reportData
}
init(from decoder: Decoder) {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.reportType = try container.decode(String.self, forKey: .reportType)
switch ReportTypes(rawValue: self.reportType) {
case .simple:
???
case .advance:
???
}
}
}
Please look at the switch cases and i'm not sure what to do. I need a solution similar to do this.
Workaround:
The workaround is that to mode that reportType inside the report {} structure and then follow this question How can i parse an Json array of a list of different object using Codable?
New Structure
{
"reports": [
{
"reportType": "advance",
"reportData": {
"reportType": "advance",
"value1": "true"
}
},
{
"reportType": "simple",
"reportData": {
"reportType": "simple",
"value3": "false",
"value": "sample"
}
}
]
}
So it worked out for me this way.
But if changing the structure is not what you can afford then this will not work.
Other possible solution I see and later Question: How to Access value of Codable Parent struct in a nested Codable struct is storing reportType in variable currentReportType from init(from decoder: Decoder) and then write another decoder for struct reportData that will handle decoding based on the value stored in var currentReportType. Write it by following the first link shared.
A reasonable way to decode this JSON is an enum with associated values because the type is known.
Create two structs for the objects representing the data in reportData
struct AdvancedReport: Decodable {
let value1: String
}
struct SimpleReport: Decodable {
let value3, value: String
}
and adopt Decodable in ReportType
enum ReportType: String, Decodable {
case simple, advance
}
The two main structs are the struct Response which is the root object (I renamed it because there are too many occurrences of the term Report) and the mentioned enum with associated values. The key reportType in the child dictionary is not being decoded because it's redundant.
struct Response: Decodable {
let reports : [Report]
}
enum Report: Decodable {
case simple(SimpleReport)
case advance(AdvancedReport)
private enum CodingKeys: String, CodingKey {
case reportType, reportData
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let reportType = try container.decode(ReportType.self, forKey: .reportType)
switch reportType {
case .simple: self = .simple(try container.decode(SimpleReport.self, forKey: .reportData))
case .advance: self = .advance(try container.decode(AdvancedReport.self, forKey: .reportData))
}
}
}
After decoding the JSON you can switch on reports
for report in response.reports {
switch report {
case .simple(let simpleReport): print(simpleReport)
case .advance(let advancedReport): print(advancedReport)
}
}

Swift JSON object with name of something/Integer

I'm using TMDB api and fetching tv show seasons, but seasons I get back are not inside array, but as objects with names: season/1, season/2. I need to be able to parse tv show with any number of seasons
Is there a way I can convert this to array without worring about how many seasons does the show have?
struct Result: Codable {
var season1: Season?
var season2: Season?
var id: Int?
enum CodingKeys: String, CodingKey {
case season1
case season2
case id
}
}
struct Season: Codable {
var id: Int?
enum CodingKeys: String, CodingKey {
case id
}
}
{
"id" : 1234,
"season/1": {
"id": 1234
},
"season/2": {
"id": 12345
}
}
EDIT:
Found a solution in dynamic coding keys
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
var tempArray = [TestSeason]()
for key in container.allKeys {
if key.stringValue.contains("season/") {
let decodedObject = try container.decode(TestSeason.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
tempArray.append(decodedObject)
} else {
print("does not contain season \(key.stringValue)")
}
}
season = tempArray
}
You're getting back a Dictionary, which you can access directly without using your Results struct. The dictionary probably provides a more flexible way of accessing the data than an array, but can also easily be converted to an Array.
As you haven't stated how you'd like the output, the below will convert them to an array of tuples, where each tuple is (season, id)
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let results = try decoder.decode([String:Season].self, from: data)
.map{($0.key, $0.value.id )}
print(results) // [("season/2", 12345), ("season/1", 1234)]
} catch {
print(error)
}
Edit: You also don't need the CodingKeys in Season as it can be inferred from the properties. All you need is
struct Season: Codable {
let id: Int
}

JSONSerializer for different json object

I have different type of dataset in the given json.
Courses json
{"name" : "University of Florida",
"data": {"GivenCourse" :{"name" : "Introduction to Computer Science", "tuition": 3000}},
"type" : "Courses" }
Professors json
{"name" : "University of Florida",
"data": {"Professor" :{"name" : "Dr.Francis Tudeluv", "age" :53}},
"type" : "Professor" }
I could able to write two structs, one is for Professor and the other one is Courses. However, as you see there are several common elements in the json object except the data. How that could be better handled?
Courses Struct is as follows:
struct Courses: Codable {
let name: String
let data: DataClass
let type: String
}
// MARK: - DataClass
struct DataClass: Codable {
let givenCourse: GivenCourse
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
}
}
// MARK: - GivenCourse
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
Professors Class
// MARK: - Welcome
struct Welcome: Codable {
let name: String
let data: DataClass
let type: String
}
// MARK: - DataClass
struct DataClass: Codable {
let professor: Professor
enum CodingKeys: String, CodingKey {
case professor = "Professor"
}
}
// MARK: - Professor
struct Professor: Codable {
let name: String
let age: Int
}
My suggestion is to declare DataClass as enum with associated types
enum DataClass {
case course(Course), professor(Professor)
}
and Type as decodable enum
enum Type : String, Decodable {
case courses = "Courses", professor = "Professor"
}
Then implement init(from decoder and decode the different types depending on the type value. The keys GivenCourse and Professor are ignored.
struct Root: Decodable {
let name: String
let data: DataClass
let type: Type
private enum CodingKeys : String, CodingKey { case name, data, type }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.type = try container.decode(Type.self, forKey: .type)
switch type {
case .courses: let courseData = try container.decode([String:Course].self, forKey: .data)
data = .course(courseData.values.first!)
case .professor: let professorData = try container.decode([String:Professor].self, forKey: .data)
data = .professor(professorData.values.first!)
}
}
}
// MARK: - Professor
struct Professor: Decodable {
let name: String
let age: Int
}
// MARK: - Course
struct Course: Decodable {
let name: String
let tuition: Int
}
let jsonString = """
{"name" : "University of Florida",
"data": {"GivenCourse" :{"name" : "Introduction to Computer Science", "tuition": 3000}},
"type" : "Courses" }
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
print(error)
}
If the possible types of "data" will remain fairly small, I would just use optionals for each possible type as concrete members of DataClass, with the CodingKeys fully specified, like so:
struct Professor: Codable {
let name: String
let age: Int
}
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
struct DataClass: Codable {
var professor: Professor?
var givenCourse: GivenCourse?
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
case professor = "Professor"
}
}
struct SchoolData: Codable {
let name: String
let data: DataClass
}
This will correctly parse the two JSON samples you have, leaving the unused type nil in the DataClass. You could add some computed variables to DataClass if you like for convenient semantics, for example
var isProfessor: Bool {
get {
return nil != self.professor
}
}
This structure also lets you handle the case (if it's possible) of a record having both course and professor data - the DataClass will be able to parse both in one JSON record if it finds it.
Using generic and manual encode/decode:
struct MyDataContainer<T: Codable>: Codable {
let name: String
let data: T
let type: String
}
struct Professor: Codable {
let name: String
let age: Int
}
enum DataKind {
case unknown
case professor(Professor)
case givenCourse(GivenCourse)
}
struct GivenCourse: Codable {
let name: String
let tuition: Int
}
// MARK: - DataClass
struct DataClass: Codable {
let dataKind: DataKind
enum CodingKeys: String, CodingKey {
case professor = "Professor"
case givenCourse = "GivenCourse"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if values.contains(.givenCourse) {
let course = try values.decode(GivenCourse.self, forKey: .givenCourse)
dataKind = .givenCourse(course)
} else if values.contains(.professor) {
let professor = try values.decode(Professor.self, forKey: .professor)
dataKind = .professor(professor)
} else {
dataKind = .unknown
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch dataKind {
case .givenCourse(let course):
try container.encode(course, forKey: .givenCourse)
case .professor(let professor):
try container.encode(professor, forKey: .professor)
case .unknown: break
}
}
}
MyDataContainer<DataClass>
You can simply include both Optional givenCourse and professor models in your DataClass. Each possible model will be decoded thanks to the Codable protocol.
Edit DataClass:
struct DataClass: Codable {
let givenCourse: GivenCourse?
let professor: Professor?
enum CodingKeys: String, CodingKey {
case givenCourse = "GivenCourse"
case professor = "Professor"
}
}
Use/Debug:
let decodedData = try! JSONDecoder().decode(Response.self, from: jsonData)
if decodedData.type == "Professor" {
print(decodedData.data.professor)
} else if decodedData.type == "Courses" {
print(decodedData.data.givenCourse)
}

Swifty JSON: convert hierarchical list of objects in JSON arrays to hierarchical Swift objects

I have a below JSON format which belongs to a shop which inside this json object, Data field is a JSON array consists of a hierarchy of product categories which categories are stored in a nested hierarchical format (Each category have children and each child may have its own children, etc.).
{"Data":[
{"ID":1,"ParentCategoryId":0,"Name": "Parent1","Children":
[
{"ID":2,"ParentCategoryId":1,"Name": "P1Child1","Children":[]},
{"ID":3,"ParentCategoryId":1,"Name": "P1Child2","Children":[]},
{"ID":4,"ParentCategoryId":1,"Name": "P1Child3","Children":[]},
{"ID":5,"ParentCategoryId":1,"Name": "P1Child4","Children":[]},
]
},
{"ID":6,"ParentCategoryId":0,"Name": "Parent2","Children":
[
{"ID":7,"ParentCategoryId":6,"Name": "P2Child1","Children":[]},
{"ID":8,"ParentCategoryId":6,"Name": "P2Child2","Children":[]},
{"ID":9,"ParentCategoryId":6,"Name": "P2Child3","Children":[]}
]
}
]
}
Reading this format using Swifty JSON usign json["Data"].array returns a flat list of categories which have no hierarchy.
I want to know how can I read this hierarchical JSON object while preserving its structure.
Here is current structure of my Model Object (which is not included Children filed but must be modified to be so):
open class ProductCategory: NSObject {
var idd : NSInteger
var name : String
var parentCategoryId : NSInteger
...
}
Here's how you could model this using Codable
struct MyData: Codable {
let data: [Datum]
enum CodingKeys: String, CodingKey {
case data = "Data"
}
}
struct Datum: Codable {
let id, parentCategoryID: Int
let name: String
let children: [Datum]
enum CodingKeys: String, CodingKey {
case id = "ID"
case parentCategoryID = "ParentCategoryId"
case name = "Name"
case children = "Children"
}
}
do {
let myData = try JSONDecoder().decode(MyData.self, from: yourJsonData)
} catch {
print(error)
}
If you can consider moving from swiftyjson to JSONSerialization the following should give you the structure you are after
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
if let categories = json["Data"] as? [[String: Any]] {
//map data to your classes here
}
}
} catch {
print("Decode failed: \(error)")
}
If you use Codable, you don't need SwiftyJSON, you could just do :
do {
let productsData = try JSONDecoder().decode(ProductsDataResponse.self, from: (jsonString.data(using: .utf8))!)
} catch let e {
print("Couldn't Parse data because... \(e)")
}
Where ProductsDataResponse is :
struct ProductsDataResponse: Codable {
let data: [ProductCategory]
enum CodingKeys: String, CodingKey {
case data = "Data"
}
}

Iterating through dictionary keys of decoded JSON in Swift

I received excellent help in my previous question on how to set up my foundational JSON model. I am able to parse any values I want.
While I can parse any values I want, I am only able to access symbols or other values separately using dot notation.
btcSymbol = rawResponse.btc?.symbol
ethSymbol = rawResponse.eth?.symbol
I found other questions about iterating through dictionaries like Iterating Through a Dictionary in Swift but these examples are basic arrays and not multi-nested dictionaries using Swift's new protocols.
I want to be able to:
1. Iterate through the JSON and extract only the symbols from the CMC API.
2. Have a model where I am able to iterate all values of each currency separately so that I can later send those values to a table view for example.
BTC | name | symbol | marketCap | MaxSupply
ETH | name | symbol | marketCap | MaxSupply
Would restructuring my already existing model be the best solution? After my model is built would a standard for in loop or map be better?
JSONModel
struct RawServerResponse : Codable {
enum Keys : String, CodingKey {
case data = "data"
}
let data : [String:Base]
}
struct Base : Codable {
enum CodingKeys : String, CodingKey {
case id = "id"
case name = "name"
case symbol = "symbol"
}
let id : Int64
let name : String
let symbol : String
}
struct Quote : Codable {
enum CodingKeys : String, CodingKey {
case price = "price"
case marketCap = "market_cap"
}
let price : Double
let marketCap : Double
}
extension RawServerResponse {
enum BaseKeys : String {
case btc = "1"
case eth = "1027"
}
var btc : Base? { return data[BaseKeys.btc.rawValue] }
var eth : Base? { return data[BaseKeys.eth.rawValue] }
}
extension Base {
enum Currencies : String {
case usd = "USD"
}
var usd : Quote? { return quotes[Currencies.usd.rawValue]}
}
struct ServerResponse: Codable {
let btcName: String?
let btcSymbol: String?
init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)
btcSymbol = rawResponse.btc?.symbol
JSON
{
"data": {
"1": {
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"website_slug": "bitcoin",
"rank": 1,
"circulating_supply": 17041575.0,
"total_supply": 17041575.0,
"max_supply": 21000000.0,
"quotes": {
"USD": {
"price": 8214.7,
"volume_24h": 5473430000.0,
"market_cap": 139991426153.0,
"percent_change_1h": 0.09,
"percent_change_24h": 2.29,
"percent_change_7d": -2.44
}
}
}
At least I'd recommend to map the data dictionary to get the symbol as key rather than the id, by the way if the keys are camelCaseable and you pass the .convertFromSnakeCase key decoding strategy you don't need any coding keys, for example
struct RawServerResponse : Codable {
var data = [String:Base]()
private enum CodingKeys: String, CodingKey { case data }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let baseDictionary = try container.decode([String:Base].self, forKey: .data)
baseDictionary.forEach { data[$0.1.symbol] = $0.1 }
}
}
struct Base : Codable {
let id : Int64
let name : String
let symbol : String
let quotes : [String:Quote]
}
struct Quote : Codable {
let price : Double
let marketCap : Double
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let rawResponse = try decoder.decode(RawServerResponse.self, from: data)
for (symbol, base) in rawResponse.data {
print(symbol, base.quotes["USD"]?.marketCap)
// ETH Optional(68660795252.0)
// BTC Optional(139991426153.0)
}
} catch { print(error) }