How to make structs for this JSON with Decodable protocol? - json

I've got this JSON:
{
"$type": "DTOMapper.DTOResponseList`1[[Telemed.Dto.DTOTip, Telemed.Dto]], DTOMapper",
"ResponseList": {
"$type": "System.Collections.Generic.List`1[[Telemed.Dto.DTOTip, Telemed.Dto]], mscorlib",
"$values": [
{
"$type": "Telemed.Dto.DTOTip, Telemed.Dto",
"Title": "NO TE JUNTES CON LUQUITAS",
"Text": "Porque si tenes un amigo lucas y otro amigo lucas, tenés dos lucas. Pero no te sirven para pagar nada",
"GroupName": "TGC.Tips1",
"ConfigurationPath": "TelemedGlobalConfig>Tips>Tips[0]"
},
{
"$type": "Telemed.Dto.DTOTip, Telemed.Dto",
"Title": "no te emborraches en las fiestas",
"Text": "Terminarás pateando globos",
"GroupName": "TGC.Tips2",
"ConfigurationPath": "TelemedGlobalConfig>Tips>Tips[1]"
}
]
},
"StatusCode": 200,
"ErrorId": 0
}
And I'm trying to get access to Title and Text from the array $values.
Here are my current structs but Root gives me errors.
struct Root : Decodable { // <<< Type 'Root' does not conform to protocol 'Decodable'
private enum CodingKeys : String, CodingKey { case responseList = "ResponseList" }
let responseList : ResponseList // <<< Use of undeclared type 'ResponseList'
}
struct Values : Decodable {
private enum CodingKeys : String, CodingKey {
case title = "Title"
case text = "Text"
}
let title : String
let text : String
}
What is the correct way to make this? Also, do I have to make a struct and let for everything? Even for things I won't use, like $type, GroupName?

What is the correct way to make this?
do {
let res = try JSONDecoder().decode(Root.self, from: data)
}
catch {
print(error)
}
struct Root: Codable {
let type: String
let responseList: ResponseList
let statusCode, errorID: Int
enum CodingKeys: String, CodingKey {
case type = "$type"
case responseList = "ResponseList"
case statusCode = "StatusCode"
case errorID = "ErrorId"
}
}
// MARK: - ResponseList
struct ResponseList: Codable {
let type: String
let values: [Value]
enum CodingKeys: String, CodingKey {
case type = "$type"
case values = "$values"
}
}
// MARK: - Value
struct Value: Codable {
let title, text:String // left only <<< access to Title and Text
enum CodingKeys: String, CodingKey {
case title = "Title"
case text = "Text"
}
}
Do I have to make a struct and let for everything? Even for things I won't use, like $type, GroupName?
No only properties that you'll use

You could try this:
struct YourStructName: Codable {
var statusCode: Int
var errorId: Int
var type: String // Maybe make this an enum case
var response: Response
enum CodingKeys: String, CodingKey {
case statusCode = "StatusCode"
case errorId = "ErrorId"
case type = "$type"
case response = "ResponseList"
}
struct Response: Codable {
var type: String // Again, consider making this an enum case
var values: [ResponseValue]
enum CodingKeys: String, CodingKey {
case type = "$type"
case values = "$values"
}
struct ResponseValue: Codable {
var title: String
var text: String
enum CodingKeys: String, CodingKey {
case title = "Title"
case text = "Text"
}
}
}
}

Related

SwiftUI - Display nested Array in JSON response - try breaking up the expression into distinct sub-expressions

I have the following JSON response.
{
"results":[
{
"TrimName":"Truck",
"VIN":"GTJ3E18JF164623",
"Price": 47600,
"Odometer": 35511,
"OdometerType": "Miles",
"OptionCodeSpecs":{
"C_SPECS":{
"code":"C_SPECS",
"name":"Specs",
"options":[
{
"code":"SPECS_ACCELERATION",
"name":"5.2 sec",
"long_name":"5.2 seconds",
"description":"0-60 mph"
},
{
"code":"SPECS_TOP_SPEED",
"name":"140 mph",
"long_name":"140 miles per hour",
"description":"Top Speed"
},
{
"code":"SPECS_RANGE",
"name":"264 mi",
"long_name":"264 miles",
"description":"range (EPA)"
}
]
}
}
}
],
"total_matches_found":"53"
}
I built the Struct using app.quicktype.io.
// MARK: - InventoryModel
struct InventoryModel: Codable {
let results: [Result]
let totalMatchesFound: String
enum CodingKeys: String, CodingKey {
case results
case totalMatchesFound = "total_matches_found"
}
}
// MARK: - Result
struct Result: Codable {
let trimName, vin: String
let price, odometer: Int
let odometerType: String
let optionCodeSpecs: OptionCodeSpecs
enum CodingKeys: String, CodingKey {
case trimName = "TrimName"
case vin = "VIN"
case price = "Price"
case odometer = "Odometer"
case odometerType = "OdometerType"
case optionCodeSpecs = "OptionCodeSpecs"
}
}
// MARK: - OptionCodeSpecs
struct OptionCodeSpecs: Codable {
let cSpecs: CSpecs
enum CodingKeys: String, CodingKey {
case cSpecs = "C_SPECS"
}
}
// MARK: - CSpecs
struct CSpecs: Codable {
let code, name: String
let options: [Option]
}
// MARK: - Option
struct Option: Codable {
let code, name, longName, optionDescription: String
enum CodingKeys: String, CodingKey {
case code, name
case longName = "long_name"
case optionDescription = "description"
}
}
I am able to successfully retrieve the JSON response successfully.
#Published var getInventoryQuery: InventoryModel = InventoryModel(results: [], totalMatchesFound: "")
In my ContentView I am able to display the data at the Results object, but I am unable to get to the options array in the OptionCodeSpecs object without the following error:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
Xcode Version 12.5.1 (12E507)
#ObservedObject var inv = GetInventory()
The following works great. Code tripped out for clarity.
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 10){
ForEach(inv.getInventoryQuery.results, id: \.trimName) { inventory in
VStack(alignment: .leading) {
VStack(alignment: .center) {
Text(inventory.trimName)
However, when I try and get to the options array, I get the following error:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
HStack(alignment: .center){
ForEach(inv.getInventoryQuery.results[inventory].optionCodeSpecs.cSpecs.options) { options in
Text(options.longName)
.font(.headline)
.fontWeight(.light)
Image(systemName: "battery.100")
.renderingMode(.original)
}
}

Swift Codable: generic solution for different server answers at same key [duplicate]

I try to parse an api returning a json object. My problem is that some keys are sometime a string, sometime an object like the key "Value" in the following example:
[
{
"Description": null,
"Group": "Beskrivning av enheten",
"GroupDescription": null,
"Id": "Description",
"Name": "Mer om enheten",
"Value": "Det finns möjlighet till parkering på gatorna runt om, men det är kantstenar och ganska branta backar för att komma upp till lekplatsen.\r\n\r\nUtanför själva lekplatsen finns en gungställning med en plan omväg in. Alla lekredskap står i sandytor, det finns många kanter. Runt hela lekplatsen går ett staket med öppningar i olika riktningar."
},
{
"Description": null,
"Group": "Bilder och film",
"GroupDescription": null,
"Id": "Image",
"Name": "Huvudbild",
"Value": {
"__type": "FileInfo",
"Id": "8871b3b1-14f4-4054-8728-636d9da21ace",
"Name": "ullerudsbacken.jpg"
}
}
]
My struct looks like this:
struct ServiceUnit: Codable {
let description: String?
let group: String?
let groupDescription: String?
let id: String
let name: String
var value: String?
struct ServiceUnitTypeInfo: Codable {
let id: String
let singularName: String?
enum CodingKeys: String, CodingKey {
case id = "Id"
case singularName = "SingularName"
}
}
let serviceUnitTypeInfo: ServiceUnitTypeInfo?
let values: [String]?
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
case serviceUnitTypeInfo = "ServiceUnitTypeInfo"
case values = "Values"
case image = "Image"
}
}
I have to admin that I am totally lost (yes, I am a beginner in swift) and I can't find a solution to my problem. I understand that I have to use a custom init, but I don't know how.
You can try
struct Root: Codable {
let description,id: String
let group,groupDescription: String?
let name: String
let value: MyValue
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
}
}
enum MyValue: Codable {
case string(String)
case innerItem(InnerItem)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(InnerItem.self) {
self = .innerItem(x)
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
case .innerItem(let x):
try container.encode(x)
}
}
}
struct InnerItem: Codable {
let type, id, name: String
enum CodingKeys: String, CodingKey {
case type = "__type"
case id = "Id"
case name = "Name"
}
}
do {
let result = try JSONDecoder().decode([Root].self,from:data)
print(result)
}
catch {
print(error)
}
Building on the answer of #Sh_Khan and to answer the question of #Nikhi in the comments (how can you access the values) I like to do add this to the enum declaration:
var innerItemValue: InnerItem? {
switch self {
case .innerItem(let ii):
return ii
default:
return nil
}
}
var stringValue: String? {
switch self {
case .string(let s):
return s
default:
return nil
}
}

parsing array multidimension using SwiftJson

I'm trying to Parse JSON with code and structure like this:
"custom_attributes": [
{
"attribute_code": "api_attribute",
"value": [
{
"color": [
{
"value_index": "4",
"label": "Red",
"product_super_attribute_id": "1",
"default_label": "Red",
"store_label": "Red",
"use_default_value": true
}
]
},
{
"size": [
{
"value_index": "13",
"label": "35",
"product_super_attribute_id": "2",
"default_label": "35",
"store_label": "35",
"use_default_value": true
}
]
},
I've tried code like this:
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
if let resData = swiftyJsonVar["custom_attributes"]["value"]["color"].arrayObject {
self.arrImage = resData as! [[String:AnyObject]]
but I did not get json results at all. when i try if let resData = swiftyJsonVar["custom_attributes"].arrayObject i get all result
custom_attributes , value are arrays
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!).dictionaryValue
if let resData = swiftyJsonVar["custom_attributes"]?.arrayValue , let sec = resData.first?.dictionaryValue["value"]?.arrayValue , let color = sec.first?.dictionaryValue["color"]?.arrayValue {
print("dhjjhdhdsjhdsjdshjdsjhds ",color)
}
else {
}
}
}
Edit : accessing size
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!).dictionaryValue
if let resData = swiftyJsonVar["custom_attributes"]?.arrayValue , let sec = resData.first?.dictionaryValue["value"]?.arrayValue , let color = sec[1].dictionaryValue["size"]?.arrayValue {
print("dhjjhdhdsjhdsjdshjdsjhds ",size)
}
else {
}
}
}
btw recommend
struct Root: Codable {
let customAttributes: [CustomAttribute]
enum CodingKeys: String, CodingKey {
case customAttributes = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let attributeCode: String
let value: [Value]
enum CodingKeys: String, CodingKey {
case attributeCode = "attribute_code"
case value
}
}
struct Value: Codable {
let color: [Color]
}
struct Color: Codable {
let valueIndex, label, productSuperAttributeID, defaultLabel: String
let storeLabel: String
let useDefaultValue: Bool
enum CodingKeys: String, CodingKey {
case valueIndex = "value_index"
case label
case productSuperAttributeID = "product_super_attribute_id"
case defaultLabel = "default_label"
case storeLabel = "store_label"
case useDefaultValue = "use_default_value"
}
}
Instead of manually parsing whole response each time I would suggest you
use to go for much powerful API provided by Apple to us is Codable.
You can read more about codable here: https://developer.apple.com/documentation/swift/codable
You can define coding keys you want to parse and get the ready models from Codable.
Coding Example:
Create your model accordingly
struct Root: Codable {
let customAttributes: [CustomAttribute]
enum CodingKeys: String, CodingKey {
case customAttributes = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let attributeCode: String
let value: [Value]
enum CodingKeys: String, CodingKey {
case attributeCode = "attribute_code"
case value
}
}
struct Value: Codable {
let color: [Color]
}
struct Color: Codable {
let valueIndex, label, productSuperAttributeID, defaultLabel: String
let storeLabel: String
let useDefaultValue: Bool
enum CodingKeys: String, CodingKey {
case valueIndex = "value_index"
case label
case productSuperAttributeID = "product_super_attribute_id"
case defaultLabel = "default_label"
case storeLabel = "store_label"
case useDefaultValue = "use_default_value"
}
}
Usage:
Alamofire.request("http://adorableprojects.store/rest/V1/detailProduct/configurable-product").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
let customAttributesResponse = swiftyJsonVar["custom_attributes"]
do {
// You can parse response with codable's here
let data = try customAttributesResponse.rawData()
let customAttributes = try JSONDecoder().decode([CustomAttribute].self, from:data)
print(customAttributes)
}
catch {
debugPrint("\(#function)--\(error)")
}
}
}

Swift 4 decodable nested json with random key attributes

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.

Swift Codable expected to decode Dictionary<String, Any>but found a string/data instead

I have been working with the Codable protocol
Here is my JSON file :
{
"Adress":[
],
"Object":[
{
"next-date":"2017-10-30T11:00:00Z",
"text-sample":"Some text",
"image-path":[
"photo1.png",
"photo2.png"
],
"email":"john.doe#test.com",
"id":"27"
},
{
"next-date":"2017-10-30T09:00:00Z",
"text-sample":"Test Test",
"image-path":[
"image1.png"
],
"email":"name.lastename#doe.com",
"id":"28"
}
]
}
I only have to focus on the Object array, and the "image-path" array can contain 0, 1, or 2 strings.
So here is my implementation:
struct Result: Codable {
let Object: [MyObject]
}
struct MyObject: Codable {
let date: String
let text: String
let image: [String]
let email: String
let id: String
enum CodingKeys: String, CodingKey {
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
init() {
self.date = ""
self.text = ""
self.image = []
self.email = ""
self.id = ""
}
}
I call it from my service class after requesting and getting the JSON data this way:
if let data = response.data {
let decoder = JSONDecoder()
let result = try! decoder.decode(Result, from: data)
dump(result.Object)
}
Everything is working except the [String] for the image property
But it can't compile, or I get an "Expected to decode..." error.
How should I handle the nil/no data scenario?
I have made a small change in your MyObject struct, i.e.,
1. Marked all properties as optionals
2. Removed init() (I don't think there is any requirement of init() here.)
3. Use Result.self instead of Result in decoder.decode(...) method
struct MyObject: Codable
{
let date: String?
let text: String?
let image: [String]?
let email: String?
let id: String?
enum CodingKeys: String, CodingKey
{
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
}
To test the above, I have used the below code and it is working fine.
let jsonString = """
{"Adress": [],
"Object": [{"next-date": "2017-10-30T11:00:00Z",
"text-sample": "Some text",
"image-path": ["photo1.png", "photo2.png"],
"email": "john.doe#test.com",
"id": "27"},
{"next-date": "2017-10-30T09:00:00Z",
"text-sample": "Test Test",
"image-path": ["image1.png"],
"email": "name.lastename#doe.com",
"id": "28"}
]
}
"""
if let data = jsonString.data(using: .utf8)
{
let decoder = JSONDecoder()
let result = try? decoder.decode(Result.self, from: data) //Use Result.self here
print(result)
}
This is the result value that I am getting: