I have a JSON data structure using unique keys that are created on upload. I can read all of it if I read each dictionary item line by line. However, I'm trying to modify my code to use the Swift 4 codable properties.
Doing the Ray Wenderlich tutorial and reading the Ultimate Guide to JSON Parsing with Swift unfortunately did not propel me to genius status.
The JSON looks like this simple example: NOTE that keys, like "123", "456", "case1", "case2", "u1", "u2" are not known at run time.
let json = """
{
"things" : {
"123" : {
"name" : "Party",
"owner" : "Bob",
"isActive" : true,
"cases" : {
"case1" : {
"no" : 1
},
"case2" : {
"no" : 2
}
}
},
"456" : {
"name" : "Bus",
"owner" : "Joe",
"isActive" : true
}
},
"users" : {
"u1" : {
"name" : "Summer"
},
"u2" : {
"name" : "Daffy"
}
}
}
"""
Following this SO question on flattening JSON, I was able to create a decoder for most of my data, but not for the nested dictionaries (in the example, cases is acting like a nested dictionary). I am sure that I am missing something simple.
If I attempt to include the commented out portion, the playground will not run, no error is given.
struct Thing: Decodable {
let id: String
let isActive: Bool
let name: String
let owner: String
//var cases = [Case]()
init(id: String, isActive: Bool, name: String, owner: String){//}, cases: [Case]?) {
self.id = id
self.isActive = isActive
self.name = name
self.owner = owner
//self.cases = cases ?? [Case(id: "none", caseNumber: 0)]
}
}
struct User: Decodable {
let id: String
let name: String
}
struct Case: Decodable {
let id: String
let caseNumber: Int
}
struct ResponseData: Decodable {
var things = [Thing]()
var users = [User]()
enum CodingKeys: String, CodingKey {
case trips
case users
}
private struct PhantomKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
}
private enum ThingKeys: String, CodingKey {
case isActive, name, owner, cases
}
private enum UserKeys: String, CodingKey {
case name
}
private enum CaseKeys: String, CodingKey {
case id
case caseNumber = "no"
}
init(from decoder: Decoder) throws {
let outer = try decoder.container(keyedBy: CodingKeys.self)
let thingcontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .things)
for key in thingcontainer.allKeys {
let aux = try thingcontainer.nestedContainer(keyedBy: ThingKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let owner = try aux.decode(String.self, forKey: .owner)
let isActive = try aux.decode(Bool.self, forKey: .isActive)
// let c = try aux.nestedContainer(keyedBy: CaseKeys.self, forKey: .cases)
// var cases = [Case]()
// for ckey in c.allKeys {
// let caseNumber = try c.decode(Int.self, forKey: .caseNumber)
// let thiscase = Case(id: ckey.stringValue, caseNumber: caseNumber)
// cases.append(thiscase)
// }
let thing = Thing(id: key.stringValue, isActive: isActive, name: name, owner: owner)//, cases: cases)
things.append(thing)
}
let usercontainer = try outer.nestedContainer(keyedBy: PhantomKeys.self, forKey: .users)
for key in usercontainer.allKeys {
let aux = try usercontainer.nestedContainer(keyedBy: UserKeys.self, forKey: key)
let name = try aux.decode(String.self, forKey: .name)
let user = User(id: key.stringValue,name: name)
users.append(user)
}
}
}
It works for the things and users, but I have to ignore the cases. See the output of print in comments//.
let data = json.data(using: .utf8)!
let things = try JSONDecoder().decode(ResponseData.self, from: data).things
print(things[0])
//Thing(id: "456", isActive: true, name: "Bus", owner: "Joe")
let users = try JSONDecoder().decode(ResponseData.self, from: data).users
print(users[0])
//User(id: "u1", name: "Summer")
I have tried to use the guidance from this SO question on decoding that seems much cleaner to me, but I have not successfully implemented it.
This code is also a GIST
My question is twofold:
- How can I get the Case data as a nested array in my Thing?
- Can you suggest a cleaner/shorter way to code this? It feels like I'm repeating things, but I have seen this kind of wrapper structure in several examples for JSON encoding/decoding.
You can get keys from your current json as
After that query in the loop by each key retrieved
You can try something like this:
EDIT I initially missunderstood your question , here is your code improved to obtain the cases. It was a quick job so might not be optimal, but you get the idea:
This produce this output: