Retrieve Array from Firebase Firestore using REST API on iOS - ios

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 }

Related

Managing Dynamic Keys in response through Codable Protocol

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")

Parse JSON with different keys to same object using Codable in Swift

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)!)

Swift 4.1 Decodable Can't decode nested array with nestedContainer

Trying to write a simple Swift 4.1 using Codable to parse json.
I have a struct like this:
struct GameCharacter : Codable {
var name : String
var weapons : [Weapon]
enum CodingKeys : String, CodingKey {
case name
case weapons
}
init(from decoder: Decoder) {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let weaponsContainer = try container.nestedContainer(keyedBy: Weapon.CodingKeys.self, forKey: .weapons)
self.weapons = try weaponsContainer.decode([Weapon].self, forKey: .weapons)
} catch let error {
print("error: \(error)")
fatalError("error is \(error)")
}
}
}
and another like this:
struct Weapon : Codable {
var name : String
enum CodingKeys : String, CodingKey {
case name
}
init(from decoder: Decoder) {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
} catch let error {
print("error: \(error)")
fatalError("error is \(error)")
}
}
}
I also have a struct for the wrapper like this:
struct Game : Codable {
var characters : [GameCharacter]
enum CodingKeys : String, CodingKey { case characters }
}
The json data looks like this:
{
"characters" : [{
"name" : "Steve",
"weapons" : [{
"name" : "toothpick"
}]
}]
}
However, I am always getting a typeMismatcherror error:
error: typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"characters", intValue: nil), _JSONKey(stringValue: "Index 0",
intValue: 0)], debugDescription: "Expected to decode
Dictionary but found an array instead.", underlyingError:
nil))
on this line:
let weaponsContainer = try container.nestedContainer(keyedBy: Weapon.CodingKeys.self, forKey: .weapons)
I am not sure what the issue is, as I am clearly (in my view) asking for an array of Weapons, but it thinks I am looking for a dictionary anyway.
Wondering if anyone has any insight as to what I am missing.
nestedContainers is only needed if you want to decode a sub-dictionary or sub-array into the parent struct – for example decode the weapons object into the Game struct – which is not the case because you declared all nested structs.
To decode the JSON you can omit all CodingKeys and the initializers, take advantage of the magic of Codable, this is sufficient:
struct Game : Codable {
let characters : [GameCharacter]
}
struct GameCharacter : Codable {
let name : String
let weapons : [Weapon]
}
struct Weapon : Codable {
let name : String
}
and call it
do {
let result = try JSONDecoder().decode(Game.self, from: data)
print(result)
} catch { print(error) }
Replace your struct with following no need for any custom initializers
import Foundation
struct Weapon: Codable {
let characters: [Character]
}
struct Character: Codable {
let name: String
let weapons: [WeaponElement]
}
struct WeaponElement: Codable {
let name: String
}
And create
extension Weapon {
init(data: Data) throws {
self = try JSONDecoder().decode(Weapon.self, from: data)
}
Now just
let weapon = try Weapon(json)
try this
let string = """
{
"characters" : [{
"name" : "Steve",
"weapons" : [{
"name" : "toothpick"
}]
}]
}
"""
struct GameCharacter: Codable {
let characters: [Character]
}
struct Character: Codable {
let name: String
let weapons: [Weapon]
}
struct Weapon: Codable {
let name: String
}
let jsonData = string.data(using: .utf8)!
let decodr = JSONDecoder()
let result = try! decodr.decode(GameCharacter.self, from: jsonData)
let weapon = result.characters.flatMap {$0.weapons}
for weaponname in weapon {
print(weaponname.name) //Output toothpick
}
I Have the same problem, JSONDecoder() only decode the first level of my JSON and then I solve this problem with commented these methods from the body of my class that extended from Codable
public class Response<T:Codable> : Codable {
public let data : T?
//commented this two function and my problem Solved <3
// enum CodingKeys: String, CodingKey {
// case data
// }
// required public init(from decoder: Decoder) throws {
// data = try T(from: decoder)
// }
}

Using Codable to decode complex json into trivial struct without nested structs

Lets have a json
{
"channelId": 100,
"channel_name": "STV 1",
"stream": {
"URL": "www.rtvs.sk",
"DRM": "secureMedia",
"drmKeys": ["1", "2", "3"],
"userInfo": {
"user": "Michal23",
"userIsTester": true
}
}
}
and a struct:
struct Channel : Codable {
var channelId : Int
var channelName : String
var channelUrl : URL
private enum CodingKeys : String, CodingKey {
case channelId
case channelName = "channel_name"
case channelUrl = "URL" <===??? json path somehow?
}
}
I would like to fetch URL from nested stream, but without creating nested struct for it. Is it possible? How?
Looking at the documentation, you can do it but it is more of a manual process than usual. You need to decode the nested container and then extract the information using the coding key.
//: Playground - noun: a place where people can play
import UIKit
let jsonData = """
{
"channelId": 100,
"channel_name": "STV 1",
"stream": {
"URL": "www.rtvs.sk"
}
}
""".data(using: String.Encoding.utf8)!
struct Channel {
var channelId : Int
var channelName : String
var channelUrl: URL
private enum CodingKeys : String, CodingKey {
case channelId
case channelName = "channel_name"
case stream
}
private enum AdditionalInfoKeys: String, CodingKey {
case channelUrl = "URL"
}
}
extension Channel: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
channelId = try values.decode(Int.self, forKey: .channelId)
channelName = try values.decode(String.self, forKey: .channelName)
let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .stream)
channelUrl = try additionalInfo.decode(URL.self, forKey: .channelUrl)
}
}
let decoder = JSONDecoder()
let channel = try? decoder.decode(Channel.self, from: jsonData)
print(channel)
OUTPUT: Channel(channelId: 100, channelName: "STV 1", channelUrl: www.rtvs.sk))

Swift 4 Codable array with dictionary has value of String and Array

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

Resources