struct ProductDetails:Codable {
var custom_attributes:[CustomAttributesData]
struct CustomAttributesData:Codable {
var attribute_code:String
var value:String
}
}
I have an Array of custom_attributes has dictionary with elements of attribute_code as String & value as String, but some value datatype's are in Array, due to Array I am not able to parse using codable, help me out, Thanks in advance
"custom_attributes": [
{
"attribute_code": "image",
"value": "/6/_/6.jpg"
},
{
"attribute_code": "small_image",
"value": "/6/_/6.jpg"
}
{
"attribute_code": "news_to_date",
"value": "2017-09-30 00:00:00"
},
{
"attribute_code": "category_ids",
"value": [
"2",
"120"
]
},
{
"attribute_code": "options_container",
"value": "container2"
}
]
I have added json above.
You have to add a custom initializer which distinguishes between String and [String].
This code declares value as [String] and converts a single string to an array
struct ProductDetails : Decodable {
let custom_attributes : [CustomAttributesData]
struct CustomAttributesData : Decodable {
private enum CodingKeys : String, CodingKey {
case attributeCode = "attribute_code", value
}
let attributeCode : String
let value : [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
attributeCode = try container.decode(String.self, forKey: .attributeCode)
do {
let string = try container.decode(String.self, forKey: .value)
value = [string]
} catch DecodingError.typeMismatch {
value = try container.decode([String].self, forKey: .value)
}
}
}
}
Alternatively you could use two separate properties stringValue and arrayValue
Related
I'm using REST APIs to retrieve data from my Firestore DB. I'm forced to use REST API instead of the Firebase SDK since App Clip don't allow to use the latter.
The JSON file is the following: JSON File
And, as text:
{
"name": "projects/myProject/databases/(default)/documents/Brand/rxnBLnp736gqjFBNLxxx",
"fields": {
"descrizione": {
"stringValue": "My project Brand Demo"
},
"descrizione_en": {
"stringValue": "My project Brand Demo"
},
"listaRefsLinea": {
"arrayValue": {
"values": [
{
"referenceValue": "projects/myProject/databases/(default)/documents/Linea/aeeDNuY9xEvRvyM5cxxx"
}
]
}
},
"data_consumption": {
"stringValue": "7xpISf0XxRnfrnUkNxxx"
},
"url_logo": {
"stringValue": "gs://myproject.appspot.com/FCMImages/app-demo-catalogue.png"
},
"web_url": {
"stringValue": "www.mybrand.it"
},
"nome_brand": {
"stringValue": "My project Demo"
}
},
"createTime": "2021-05-19T10:34:51.828685Z",
"updateTime": "2022-05-24T14:03:16.121296Z"
}
And I'm decoding it as follows:
import Foundation
struct BrandResponse : Codable {
let brands : [Brand_Struct]
private enum CodingKeys : String, CodingKey {
case brands = "documents"
}
}
struct StringValue : Codable {
let value : String
private enum CodingKeys : String, CodingKey {
case value = "stringValue"
}
}
struct Brand_Struct : Codable {
let url_logo : String
let web_url : String
let nome_brand : String
let descrizione : String
let listaRefsLinea : [String]
let descrizione_en : String
let data_consumption : String
private enum BrandKeys : String, CodingKey {
case fields
case listaRefsLinea
}
private enum FieldKeys : String, CodingKey {
case url_logo
case web_url
case nome_brand
case descrizione
case listaRefsLinea
case descrizione_en
case data_consumption
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: BrandKeys.self)
let fieldContainer = try container.nestedContainer(keyedBy: FieldKeys.self, forKey: .fields)
//listaRefsLinea = try containerListaRefsLinea_2.decode(ArrayValue.self, forKey: .values).referenceValue
nome_brand = try fieldContainer.decode(StringValue.self, forKey: .nome_brand).value
web_url = try fieldContainer.decode(StringValue.self, forKey: .web_url).value
url_logo = try fieldContainer.decode(StringValue.self, forKey: .url_logo).value
descrizione = try fieldContainer.decode(StringValue.self, forKey: .descrizione).value
descrizione_en = try fieldContainer.decode(StringValue.self, forKey: .descrizione_en).value
data_consumption = try fieldContainer.decode(StringValue.self, forKey: .data_consumption).value
listaRefsLinea = [""] // <-- How to read this??
}
}
My issue is that I'm not being able to read the array inside the field "listaRefsLinea". Any idea on how to achieve that? Also I'm afraid that part of the troubles come from the fact that that's a Document Reference variable and as such does not conform to the Codable protocol.
Well. listaRefsLinea is a custom object just like your StringValue
So add these structs:
// MARK: - ListaRefsLinea
struct ListaRefsLinea: Codable {
let arrayValue: ArrayValue
}
// MARK: - ArrayValue
struct ArrayValue: Codable {
let values: [Value]
}
// MARK: - Value
struct Value: Codable {
let referenceValue: String
}
and in your custom init decode it to this struct, go down the tree until you get the array and map that to a [String]:
listaRefsLinea = try fieldContainer.decode(ListaRefsLinea.self, forKey: .listaRefsLinea)
.arrayValue.values.map{ $0.referenceValue }
I need to make the codable model for the dynamic keys of dictionary coming from response below is the response I'm getting.
{
"data" : [
{
"desc1" : null,
"file1" : "uploads\/posts\/Aug-2021\/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads\/posts\/Aug-2021\/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
This desc1 and file1 is dynamic till like file1, file2 and so on, I need to have codable model for that below is my model that is not supportive.
struct ListModel: Codable {
public var data: [data]?
}
struct data: Codable {
let file : String?
let desc : String?
}
Anything support by codable protocol for that. Thanks in Advance.
You need a custom initializer. Of course this will only work if your json will always come formatted as described:
struct File {
var file: String? = ""
var desc: String? = ""
}
struct Response {
let files: [File]
let status: String
enum CodingKeys: String, CodingKey {
case files = "data", status
}
}
extension Response: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.status = try container.decode(String.self, forKey: .status)
let elements = try container.decode([[String: String?]].self, forKey: .files)
self.files = elements.reduce(into: []) {
var file = File()
for (key, value) in $1 {
if key.hasPrefix("file") {
file.file = value
} else if key.hasPrefix("desc") {
file.desc = value
}
}
$0.append(file)
}
}
}
Playground testing:
let json = """
{
"data" : [
{
"desc1" : null,
"file1" : "uploads/posts/Aug-2021/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
"""
do {
let response = try JSONDecoder().decode(Response.self, from: Data(json.utf8))
print(response)
} catch {
print(error)
}
This will print:
Response(files: [File(file: Optional("uploads/posts/Aug-2021/1629271422310452767"), desc: nil), File(file: Optional("uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"), desc: Optional("hello"))], status: "success")
I receive the following 2 responses from different APIs
{
"id": "jdu72bdj",
"userInfo": {
"name": "Sudhanshu",
"age": 28,
"country": "India"
}
}
and
{
"profileId": "jdu72bdj",
"profileDetails": {
"name": "Sudhanshu",
"age": 28,
"country": "India"
}
}
This is in context with iOS development using Swift language.
Basically the object structure is same but keys are different. I'm parsing these using Codable, but I cannot think of a way to parse using same struct. All I can think of is making 2 different structs like this -
public struct Container1: Codable {
public let id: String
public let userInfo: UserProfile?
}
and
public struct Container2: Codable {
public let profileId: String
public let profileDetails: UserProfile?
}
They both use common UserProfile struct.
public struct UserProfile: Codable {
public let name: String?
public let age: Int?
public let country: String?
}
Is there a way to use one common container struct for both responses which parse response from 2 different keys. I do not want Container1 and Container2 since they both have same structure.
Any suggestions ?
One solution is to use a custom key decoding strategy using an implementation of CodingKey found in Apple's documentation. The idea is to map the keys of both of the json messages to the properties of the struct Container that will be used for both messages.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keys in
let key = keys.last!.stringValue
switch key {
case "id", "profileId":
return AnyKey(stringValue: "id")!
case "userInfo", "profileDetails":
return AnyKey(stringValue: "details")!
default:
return keys.last!
}
})
where the custom implementation of CodingKey is
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
print(stringValue)
self.stringValue = stringValue
intValue = nil
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
and then decode both json messages the same way using the below struct
struct Container: Codable {
let id: String
let details: UserProfile
}
let result = try decoder.decode(Container.self, from: data)
You can use your own init from decoder
struct UniversalProfileContainer: Decodable {
struct UserProfile: Decodable {
var name: String
var age: Int
var country: String
}
enum CodingKeys: String, CodingKey {
case id = "id"
case profileId = "profileId"
case userInfo = "userInfo"
case profileDetails = "profileDetails"
}
let id: String
let profile: UserProfile
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try container.decode(String.self, forKey: .id)
} catch {
id = try container.decode(String.self, forKey: .profileId)
}
do {
profile = try container.decode(UserProfile.self, forKey: .userInfo)
} catch {
profile = try container.decode(UserProfile.self, forKey: .profileDetails)
}
}
}
let first = """
{
"id": "jdu72bdj",
"userInfo": {
"name": "Sudhanshu",
"age": 28,
"country": "India"
}
}
"""
let second = """
{
"profileId": "jdu72bdj",
"profileDetails": {
"name": "Sudhanshu",
"age": 28,
"country": "India"
}
}
"""
let response1 = try? JSONDecoder().decode(UniversalProfileContainer.self,
from: first.data(using: .utf8)!)
let response2 = try? JSONDecoder().decode(UniversalProfileContainer.self,
from: second.data(using: .utf8)!)
So I've been using Swift 4's new Decodable protocol and it's been great, but now I've come across an instance that I can't find an answer. I'm trying to use decodable to parse the Reddit Comments API response. Here is a quick example. (Note this isn't the full response, just something quick for example)
Here is a quick example of my problem. If you look at the "data" key inside of "children" the dictionaries contain different data. Is there a way to have two different JSON objects in the children array depending on what contents they have or based on their position in the array?
[{
"kind": "Listing",
"data": {
"modhash": "kskppiefdzafc020177a3995ccd7f13b4ba0a8ca70e691a510",
"whitelist_status": "all_ads",
"children": [{
"kind": "t3",
"data": {
"domain": "i.redd.it",
"approved_at_utc": null,
"mod_reason_by": null,
"selftext_html": "Hello world!!!"
}
}]
}
}, {
"kind": "Listing",
"data": {
"modhash": "kskppiefdzafc020177a3995ccd7f13b4ba0a8ca70e691a510",
"whitelist_status": "all_ads",
"children": [{
"kind": "t3",
"data": {
"domain": "i.redd.it",
"approved_at_utc": null,
"author": null,
"body": "Hello world"
}
}]
}
}]
Essentially what I'm curious is possible is this....
public struct CommentRoot: Decodable {
struct Datafield: Decodable {
let modhash: String
let whitelist_status: String
let children: [Comment]? // <------ Can be 1 of 2 types of comment that vary.
let after: String?
let before: String?
}
let data: Datafield
let kind: String
}
I hop it will help you
import Foundation
struct Children : Decodable {
let data : DataInfo?
let kind : String?
enum CodingKeys: String, CodingKey {
case data
case kind = "kind"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try DataInfo(from: decoder)
kind = try values.decodeIfPresent(String.self, forKey: .kind)
}
}
struct DataInfo : Decodable {
let approvedAtUtc : String?
let author : String?
let body : String?
let domain : String?
let children : [Children]?
let modhash : String?
let whitelistStatus : String?
enum CodingKeys: String, CodingKey {
case approvedAtUtc = "approved_at_utc"
case author = "author"
case body = "body"
case domain = "domain"
case children = "children"
case modhash = "modhash"
case whitelistStatus = "whitelist_status"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
approvedAtUtc = try values.decodeIfPresent(String.self, forKey: .approvedAtUtc)
author = try values.decodeIfPresent(String.self, forKey: .author)
body = try values.decodeIfPresent(String.self, forKey: .body)
domain = try values.decodeIfPresent(String.self, forKey: .domain)
children = try values.decodeIfPresent([Children].self, forKey: .children)
modhash = try values.decodeIfPresent(String.self, forKey: .modhash)
whitelistStatus = try values.decodeIfPresent(String.self, forKey: .whitelistStatus)
}
}
I have a (annoying) situation where my back-end returns an object like this:
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
where each property is an array that holds a string as its first element. In my data model struct I could declare each property as an array but that really would be ugly. I would like to have my model as such:
struct User: Codable {
var user: String
var familyName: String
}
But this of course would fail the encoding/decoding as the types don't match. Until now I've used ObjectMapper library which provided a Map object and currentValue property, with that I could declare my properties as String type and in my model init method assig each value through this function:
extension Map {
public func firstFromArray<T>(key: String) -> T? {
if let array = self[key].currentValue as? [T] {
return array.first
}
return self[key].currentValue as? T
}
}
But now that I am converting to Codable approach, I don't know how to do such mapping. Any ideas?
You can override init(from decoder: Decoder):
let json = """
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
"""
struct User: Codable {
var name: String
var familyName: String
init(from decoder: Decoder) throws {
let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
let nameArray = try container.decode([String].self, forKey: .name)
let familyNameArray = try container.decode([String].self, forKey: .familyName)
self.name = nameArray.first!
self.familyName = familyNameArray.first!
}
enum CodingKeys: String, CodingKey {
case name
case familyName
}
}
let data = json.data(using: .utf8)!
let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]
let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
let encodedStr = String(data: encodedData, encoding: .utf8)
print(encodedStr!) // {"name":"John","familyName":"Johnson"}
My tendency would be to adapt your model to the data coming in and create computed properties for use in the application, e.g.
struct User: Codable {
var user: [String]
var familyName: [String]
var userFirstName: String? {
return user.first
}
var userFamilyName: String? {
return familyName.first
}
}
This allows you to easily maintain parody with the data structure coming in without the maintenance cost of overriding the coding/decoding.
If it goes well with your design, you could also have a UI wrapper Type or ViewModel to more clearly differentiate the underlying Model from it's display.