Decodable JSONDecoder handle different coding keys for the same value - json

I'm using Swift decodable protocol to parse my JSON response:
{
"ScanCode":"4122001131",
"Name":"PINK",
"attributes":{
"type":"Product",
"url":""
},
"ScanId":"0000000kfbdMA"
}
I'm running into an issue where sometimes I get the ScanId value with a key "Id" instead of "ScanId".
Is there a way to work around that?
Thanks

You have to write a custom initializer to handle the cases, for example
struct Thing : Decodable {
let scanCode, name, scanId : String
private enum CodingKeys: String, CodingKey { case scanCode = "ScanCode", name = "Name", ScanID, Id }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scanCode = try container.decode(String.self, forKey: .scanCode)
name = try container.decode(String.self, forKey: .name)
if let id = try container.decodeIfPresent(String.self, forKey: .Id) {
scanId = id
} else {
scanId = try container.decode(String.self, forKey: .ScanID)
}
}
}
First try to decode one key, if it fails decode the other.
For convenience I skipped the attributes key

Related

Parsing Json from Web API

Hello my name is Nico,
I am a complete beginner in App programming/SwiftUI.
I am trying to parse json data from an web api but somehow I cannot parse the data correctly. I assume that my json structure is not correct but I cannot find the problem.
The Json which I get from the Web API looks something like this:
{
"pois": [
{
"id": "2635094451",
"lat": "52.410150",
"lat_s": "52.4",
"lng": "10.776630",
"lng_s": "10.8",
"street": "Röntgenstraße",
"content": "8137285512",
"backend": "0-239283152",
"type": "1",
"vmax": "50",
"counter": "0",
"create_date": "2021-11-18 13:21:50",
"confirm_date": "2021-11-18 13:21:43",
"gps_status": "-",
"info": " {\"qltyCountryRoad\":1,\"confirmed\":\"0\",\"gesperrt\":\"0\",\"precheck\":\"[Q1|21|0]\"}",
"polyline": ""
}
],
"grid": []
}
My Structure looks like this:
struct APIResponse: Codable {
let pois: [InputDataPois]
let grid: [InputDataGrid]
private enum CodingKeys: String, CodingKey {
case pois
case grid
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
}
}
struct InputDataPois: Codable, Identifiable {
let id:String
let lat:String
let lat_s:String
let lng:String
let lng_s:String
let street:String
let content:String
let backend:String
let type:String
let vmax:String
let counter:String
let create_date:String
let confirm_date:String
let gps_status:String
let info:String
let polyline:String
}
extension InputDataPois {
private enum CodingKeys: String, CodingKey {
case id
case lat
case lat_s
case lng
case lng_s
case street
case content
case backend
case type
case vmax
case counter
case create_date
case confirm_date
case gps_status
case info
case polyline
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.lat = try container.decode(String.self, forKey: .lat)
self.lat_s = try container.decode(String.self, forKey: .lat_s)
self.lng = try container.decode(String.self, forKey: .lng)
self.lng_s = try container.decode(String.self, forKey: .lng_s)
self.street = try container.decode(String.self, forKey: .street)
self.content = try container.decode(String.self, forKey: .content)
self.backend = try container.decode(String.self, forKey: .backend)
self.type = try container.decode(String.self, forKey: .type)
self.vmax = try container.decode(String.self, forKey: .vmax)
self.counter = try container.decode(String.self, forKey: .counter)
self.create_date = try container.decode(String.self, forKey: .create_date)
self.confirm_date = try container.decode(String.self, forKey: .confirm_date)
self.gps_status = try container.decode(String.self, forKey: .gps_status)
self.info = try container.decode(String.self, forKey: .info)
self.polyline = try container.decode(String.self, forKey: .polyline)
}
}
struct InputDataGrid: Codable {
}
and my Bundle like this:
extension Bundle {
func decode(_ file: String) -> [InputDataPois] {
// 1. Locate the Json File
guard let url = URL(string: file) else {
fatalError("Failed to locate \(file) in bundle")
}
// 2.Create a property for the Data
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to Load \(file) from bundle.")
}
// 3. Create a property for the data
let str = String(decoding: data, as: UTF8.self)
print("\(str)")
guard let loaded = try? JSONDecoder().decode(APIResponse.self, from: data).pois else {
fatalError("Failed to decode \(file) from bundle.")
}
// 4. Return the ready-to-use data
return loaded
}
}
And In my View I am using:
let speed: [InputDataPois] = Bundle.main.decode("https://cdn2.atudo.net/api/1.0/vl.php?type=0,1,2,3,4,5,6&box=52.36176390234046,10.588760375976562,52.466468685912744,11.159706115722656")
The error I am getting looks something like this.
ErrorMessage
ConsoleError
Thanks in advance for you help.
Remove your custom Codable implementation and coding keys. They are not necessary, the compiler can generate them for you with this simple JSON. Then everything should work.
The problem here specifically is your APIResponse decoding:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
}
To decode an APIResponse you try to decode two APIResponses. This again would try to decode APIResponse leading to endless recursion. The only reason you are not seeing that is that your JSON contains arrays making the second call to container(keyedBy:) fail. Try setting a breakpoint (or if you must a print statement) on the first line of your init(from:) and you will see that this is called a second time before it fails.
To fix that you would need to decode the actual type of your properties:
self.pois = try container.decode([InputDataPois].self, forKey: .pois)
But as I said, there is nothing in your JSON format requiring manually implementing the decoder, so the best is to let the compiler synthesize it for you.

Parsing a JSON object with dynamic content: what is the best way to do it with low cyclomatic complexity?

I have a service that returns an array of objects in this form:
{
result: [
{
objectId: "id",
type: "objectType",
content: { ... }
}, ...
]
}
The content depends on the object type. I've tried building up a custom decoder this way (ContentItem is a protocol for ClassA, B and so on):
struct ContentDataItem: Decodable {
var objectId: String?
var type: String?
var content: ContentItem?
private enum CodingKeys: String, CodingKey {
case objectId
case type
case content
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
objectId = try container.decode(String.self, forKey: .objectId)
type = try container.decode(String.self, forKey: .type)
if let type = type {
switch type {
case "typeA":
content = try container.decode(ClassA.self, forKey: .content)
case "typeB":
content = try container.decode(ClassB.self, forKey: .content)
...
}
}
}
}
This works, but I get a high cyclomatic complexity (I'm aiming for sub-10, but I have 14 different classes for content). So I've tried changing my approach, making ContentItem the superclass and changing the init into something like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
objectId = try container.decode(String.self, forKey: .objectId)
let type = try container.decode(String.self, forKey: .type)
let itemTypes: [String: ContentItem.Type] = [
"typeA": ClassA.self,
"typeB": ClassB.self,
...
]
guard let type = type, let contentItemType = itemTypes[type] else { return }
content = try container.decode(contentItemType, forKey: .content)
}
This reduces the cyclomatic complexity as I wanted, but it doesn't work anymore because the decode only returns objects of type ContentItem (the superclass), not the specific ClassA, ClassB that I want. Is there a way to make this approach work? What is wrong with it?
And more importantly, is there a more efficient way to parse this object?

Parsing JSON with wildcard keys

I'm parsing a poorly designed JSON structure in which I can expect to find values being reused as keys pointing to further data. Something like this
{"modificationDate" : "..."
"type" : "...",
"version" : 2,
"manufacturer": "<WILDCARD-ID>"
"<WILDCARD-ID>": { /* known structure */ } }
WILDCARD-ID can be just about anything at runtime, so I can't map it to a field in a struct somewhere at compile time. But once I dereference that field, its value has known structure, at which point I can follow the usual procedure for mapping JSON to structs.
I've found myself going down this path
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
let manDict = json["manufacturer"]
let data = NSKeyedArchiver.archivedData(withRootObject: manDict)
// now you have data!
but this seems very circuitous, which makes me think that maybe there's a cleaner way of accomplishing this?
You can use custom keys with Decodable, like so:
let json = """
{
"modificationDate" : "...",
"type" : "...",
"version" : 2,
"manufacturer": "<WILDCARD-ID>",
"<WILDCARD-ID>": {
"foo": 1
}
}
""".data(using: .utf8)!
struct InnerStruct: Decodable { // just as an example
let foo: Int
}
struct Example: Decodable {
let modificationDate: String
let type: String
let version: Int
let manufacturer: String
let innerData: [String: InnerStruct]
enum CodingKeys: String, CodingKey {
case modificationDate, type, version, manufacturer
}
struct CustomKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = "\(intValue)";
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
// extract all known properties
let container = try decoder.container(keyedBy: CodingKeys.self)
self.modificationDate = try container.decode(String.self, forKey: .modificationDate)
self.type = try container.decode(String.self, forKey: .type)
self.version = try container.decode(Int.self, forKey: .version)
self.manufacturer = try container.decode(String.self, forKey: .manufacturer)
// get the inner structs with the unknown key(s)
var inner = [String: InnerStruct]()
let customContainer = try decoder.container(keyedBy: CustomKey.self)
for key in customContainer.allKeys {
if let innerValue = try? customContainer.decode(InnerStruct.self, forKey: key) {
inner[key.stringValue] = innerValue
}
}
self.innerData = inner
}
}
do {
let example = try JSONDecoder().decode(Example.self, from: json)
print(example)
}
You can capture the idea of "a specific, but currently unknown key" in a struct:
struct StringKey: CodingKey {
static let modificationDate = StringKey("modificationDate")
static let type = StringKey("type")
static let version = StringKey("version")
static let manufacturer = StringKey("manufacturer")
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.init(stringValue) }
init?(intValue: Int) { return nil }
init(_ stringValue: String) { self.stringValue = stringValue }
}
With that, decoding is straightforward, and only decodes the structure that matches the key:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringKey.self)
modificationDate = try container.decode(String.self, forKey: .modificationDate)
type = try container.decode(String.self, forKey: .type)
version = try container.decode(Int.self, forKey: .version)
manufacturer = try container.decode(String.self, forKey: .manufacturer)
// Decode the specific key that was identified by `manufacturer`,
// and fail if it's missing
manufacturerData = try container.decode(ManufacturerData.self,
forKey: StringKey(manufacturer))
}

Swift 4 decoding doubles from JSON

I though I had this concept nailed!
I am sending a JSON which contains a double.
{"elementName":"Security:Driver","element_Cost":"650"}
I've created CodingKeys and a decoder extension but I still get a Type Mismatch error when I send the data.
struct ElementCosts: Content {
let elementName: String
let elementCost: Double
enum CodingKeys: String, CodingKey {
case elementCost = "element_Cost"
case elementName
}
}
extension ElementCosts: Decodable {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
elementCost = try values.decode(Double.self, forKey: .elementCost)
elementName = try values.decode(String.self, forKey: .elementName)
}
}
Looking at some of the other posts here I cannot see what I've done wrong.
I've tried to change the Data type to Int but still has the same issue.
Any ideas?
"650" is a string, not a number.
You can parse it like this
let elementCostString = try values.decode(String.self, forKey: .elementCost)
elementConst = Double(elementCostString) ?? 0
Or change it to be a String on your model, whichever works better for you.

swift4 encoding decoding for model class of nested json parsing

I have a model class of swift which was created based on a nested json response, it follows like below
struct RootClass : Codable {
let details : String?
let itemCount : Int?
let list : [List]?
enum CodingKeys: String, CodingKey {
case details = "Details"
case itemCount = "ItemCount"
case list = "List"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
details = try values.decodeIfPresent(String.self, forKey: .details)
itemCount = try values.decodeIfPresent(Int.self, forKey: .itemCount)
list = try values.decodeIfPresent([List].self, forKey: .list)
}
}
struct List : Codable {
let companyID : Int?
let employeeCount : Int?
let employeeUser : EmployeeUser?
enum CodingKeys: String, CodingKey {
case companyID = "CompanyID"
case employeeCount = "EmployeeCount"
case employeeUser = "EmployeeUser"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
companyID = try values.decodeIfPresent(Int.self, forKey: .companyID)
employeeCount = try values.decodeIfPresent(Int.self, forKey: .employeeCount)
employeeUser = try EmployeeUser(from: decoder)
}
}
struct EmployeeUser : Codable {
let mobileNumber : String?
let name : String?
enum CodingKeys: String, CodingKey {
case mobileNumber = "MobileNumber"
case name = "Name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
mobileNumber = try values.decodeIfPresent(String.self, forKey: .mobileNumber)
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
and my json response is
{
"Details": null,
"List": [
{
"CompanyID": 140,
"EmployeeUser": {
"Name": " raghu2",
"MobileNumber": "8718718710"
},
"EmployeeCount": 0
},
{
"CompanyID": 140,
"EmployeeUser": {
"Name": "new emp reg",
"MobileNumber": "1"
},
"EmployeeCount": 0
}
],
"ItemCount": 0
}
I am trying to parse it like
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(RootClass.self, from: data)
print(gitData.itemCount ?? "")
print(gitData.list![0].employeeUser?.mobileNumber ?? "")
}
catch let err {
print("Err", err)
}
I am able to get the values of root class and list but I am getting nil values under employee user section.
Your code a few problems:
All your keys are optional. The vendor API will tell you what keys are always present and which one are optional. Follow that.
decodeIfPresent will silently fail if it cannot decode a key. When debugging your app, you want things to fail with a bang so you can fix the error before going to production.
You wrote way more code than needed. All those init(from decoder: ) functions are not needed. One one did cause your problem.
Your problem was caused by this line:
struct List : Codable {
init(from decoder: Decoder) throws {
...
employeeUser = try EmployeeUser(from: decoder)
}
}
You are asking Swift to decode to same JSON to a List and a EmployeeUser object. Obviously, that's not valid. But when you decode list inside RootClass, you call decodeIfPresent:
// In Rootclass
list = try values.decodeIfPresent([List].self, forKey: .list)
This call silently failed and you never knew what the problem was!
Solution
Change how you initialize employeeUser to this:
employeeUser = try values.decodeIfPresent(EmployeeUser.self, forKey: .employeeUser)
But the most elegant solution is to delete all those init(from decoder: ). The compiler will synthesize them for you.
And finally, fix those optionals!