Swift Codable: Include dictionary key as property in decoded Codable object - json

I have a JSON object as such:
{
"red":
{
"a": 1,
"b": 2,
"c": 3
}
"yellow":
{
"a": 1,
"b": 2,
"c": 3
}
"blue":
{
"a": 1,
"b": 2,
"c": 3
}
}
I decode each of these into a Color object marked as Codable.
I would like to include the key of the object as a property of the object itself, such that I can differentiate between the keys to provide supplementary information, such as having a function that can provide a color to pair with the object (e.g. for 'red', pair it with 'blue').
How can I include the dictionary key as a property on the Codable object itself?

Based on #vadian answer, you could try this approach, using init(from decoder: Decoder) to decode the json data,
and a Colour struct
with a name and id that you can use.
struct ColorResponse: Decodable {
var red: Colour
var yellow: Colour
var blue: Colour
enum CodingKeys: String, CodingKey {
case red,yellow,blue
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
red = try container.decode(Colour.self, forKey: .red)
red.name = "red"
yellow = try container.decode(Colour.self, forKey: .yellow)
yellow.name = "yellow"
blue = try container.decode(Colour.self, forKey: .blue)
blue.name = "blue"
}
}
struct Colour: Identifiable, Decodable {
let id = UUID()
var name: String = ""
var a, b, c : Int
enum CodingKeys: String, CodingKey {
case a,b,c
}
}
struct ColorView: View {
#State var colour: Colour
var body: some View {
VStack {
Text(colour.name)
Text("\(colour.a)")
Text("\(colour.b)")
Text("\(colour.c)")
}
}
}
struct ContentView: View {
#State var colours: [Colour] = []
var body: some View {
List(colours) { col in
ColorView(colour: col)
}
.onAppear {
let json = """
{
"red": { "a": 1, "b": 2, "c": 3 },
"yellow": { "a": 1, "b": 2, "c": 3 },
"blue": { "a": 1, "b": 2, "c": 3 }
}
"""
// simulated API data
let data = json.data(using: .utf8)!
do {
let results = try JSONDecoder().decode(ColorResponse.self, from: data)
colours.append(results.blue)
colours.append(results.red)
colours.append(results.yellow)
colours.forEach{print("---> colours: \($0)")}
} catch {
print("\n---> decoding error: \n \(error)\n")
}
}
}
}

A possible way is to add a temporary struct for the color object, then decode the dictionary as [String:Temp] and map the data to the real struct. As Color is part of SwiftUI I named the struct MyColor
struct Temp: Decodable { let a, b, c : Int }
struct Root : Decodable {
let colors : [MyColor]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let colorData = try container.decode([String:Temp].self)
colors = colorData.map{ MyColor(name: $0.key, a: $0.value.a, b: $0.value.b, c: $0.value.c) }
}
}
struct MyColor : Decodable {
let name: String
let a, b, c : Int
}
Then decode
JSONDecoder().decode(Root.self...
However if the keys are static drop the Temp struct and use this
struct Root : Decodable {
let red : MyColor
let yellow : MyColor
let blue : MyColor
}
struct MyColor : Decodable {
let a, b, c : Int
}

Related

Parsing issue : APIError: keyNotFound key CodingKeys(stringValue: "data", intValue: nil)

Unable to parse using codable structs for following json
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
Error :
APIError: keyNotFound key CodingKeys(stringValue: "data", intValue:
nil) Caused by: keyNotFound(CodingKeys(stringValue: "data", intValue:
nil), Swift.DecodingError.Context(codingPath: [], debugDescription:
"No value associated with key CodingKeys(stringValue: "data",
intValue: nil) ("data").", underlyingError: nil)))
Model:
//MARK: DeviceState
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
works well for me. This is the test code I used to show how to decode the json data into your structs. Of course Item->data is here decoded as a String, as per the json you show.
struct ContentView: View {
#State var devState: DeviceState?
var body: some View {
Group {
if let devFirst = devState?.data.listDeviceStateTable.items.first {
Text("pk: \(devFirst.pk)")
}
}
.onAppear {
let json = """
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
"""
let data = json.data(using: .utf8)!
do {
devState = try JSONDecoder().decode(DeviceState.self, from: data)
print(devState)
} catch {
print("\(error)")
}
}
}
}
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
to decode Data into something else than a String, eg a ItemData, you need to remove the double quotes,
and replace the single quote with double quotes (in the value not the key).
eg. "Data": "{'state': -1, 'remainSec': 0}", to "Data": {"state": -1, "remainSec": 0},
and use the following Item struct and decoding code:
// MARK: - Item
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
pk = try container.decode(String.self, forKey: .pk)
sk = try container.decode(String.self, forKey: .sk)
do {
let theString = try container.decode(String.self, forKey: .data)
let json = theString.replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: "\"")
data = try JSONDecoder().decode(ItemData.self, from: json.data(using: .utf8)!)
} catch DecodingError.typeMismatch {
data = ItemData(state: 0, remainSec: 0) // <-- todo
}
}
}
struct ItemData: Codable {
let state, remainSec: Int
}
You need one more struct to decode data inside Item like this
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
struct ItemData: Codable {
let state, remainSec: Int
}

Parse using Codable in Swift

Need to parse this JSON in such a way that i should be able to access the benefits associated to each plan inside the "enabled" key within "items" node as below:
let items = Items[0].plans.main[0].enabled[0].text
{
"MyData": {
"data": {
"benefits": {
"B1": {
"text": "Text1"
},
"B2": {
"text": "Text2"
},
"B3": {
"text": "text3"
}
}
},
"items": [
{
"plans": {
"main": [
{
"name": "plan1",
"enabled": [
"B1",
"B2"
],
"disabled": [
"B2",
"B3"
]
}
]
}
}
]
}
}
I have tried as below to achieve but seems like this is not working
class Main: Codable {
var name: String?
var enabled: [String]?
var disabled: [String]?
enum CodingKeys: String, CodingKey {
case name = "name"
case enabled = "enabled"
case disabled = "disabled"
}
class MyData: Codable {
var benefits: [String: Benefit]?
enum CodingKeys: String, CodingKey {
case benefits = "benefits"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let propertyContainer = try container.nestedContainer(keyedBy: CustomDynamicKey.self, forKey: .benefits)
self.benefits = propertyContainer.decodeValues()
}
class Benefit: Codable {
var text: String?
enum CodingKeys: String, CodingKey {
case text = "text"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
text = try container.decode(String.self, forKey: .text)
}
}
struct CustomDynamicKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
extension KeyedDecodingContainer where Key == DynamicKey {
func decodeValues() -> [String : Benefit] {
var dict = [String : Benefit]()
for key in allKeys {
if let md = try? decode(Benefit.self, forKey: key) {
dict[key.stringValue] = md
} else {
print("unsupported key")
}
}
return dict
}
}
I tried to parse the models individually. However, i am able to access the models separately but i need to map the corresponding the benefit with the respective plan at the time of parsing JSON itself inside the required init() methods using Manual parsing.
One way could be to use JSONDecoder alongside JSONSerialization. Codables are a bit uncomfortable to use in such situations, and you can make var benefits: [String: Benefit]? optional (which you already have) and remove it from the CodingKeys enum. Then, use JSONSerialization to get the benefits field filled.
See This

Decode heterogeneous array JSON using Swift decodable

This is the JSON I am trying to decode. The value of objectType decides what object to create.
{
"options": [
{
"objectType": "OptionTypeA",
"label": "optionALabel1",
"value": "optionAValue1"
},
{
"objectType": "OptionTypeB",
"label": "optionBLabel",
"value": "optionBValue"
},
{
"objectType": "OptionTypeA",
"label": "optionALabel2",
"value": "optionAValue2"
}
]
}
Say I have the 2 Option Types defined like so
public protocol OptionType {
var label: String { get }
var value: String { get }
}
struct OptionTypeA: Decodable {
let label: String
let value: String
// and some others...
}
struct OptionTypeB: Decodable {
let label: String
let value: String
// and some others...
}
struct Option: Decodable {
let options: [OptionType]
enum CodingKeys: String, CodingKey {
case options
case label
case value
case objectType
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
var optionsContainer = try values.nestedUnkeyedContainer(forKey: .options)
var options = [OptionType]()
while !optionsContainer.isAtEnd {
let itemContainer = try optionsContainer.nestedContainer(keyedBy: CodingKeys.self)
switch try itemContainer.decode(String.self, forKey: .objectType) {
// What should I do here so that I do not have to manually decode `OptionTypeA` and `OptionTypeB`?
case "OptionTypeA": options.append()
case "OptionTypeB": options.append()
default: fatalError("Unknown type")
}
}
self.options = options
}
}
I know I can then manually decode each key in itemContainer and create the individual option type objects in the switch case. But I do not want to do that. How can I just decode these objects?
A swiftier way than a protocol for the common properties is an enum with associated values.
The Option enum decodes first the objectType – which can even be decoded as an enum – and depending on the value it decodes the different structs.
enum OptionType : String, Decodable {
case a = "OptionTypeA", b = "OptionTypeB"
}
struct Root : Decodable {
let options : [Option]
}
enum Option : Decodable {
private enum CodingKeys : String, CodingKey { case objectType }
case typeA(OptionTypeA)
case typeB(OptionTypeB)
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let typeContainer = try decoder.singleValueContainer()
let optionType = try container.decode(OptionType.self, forKey: .objectType)
switch optionType {
case .a: self = .typeA(try typeContainer.decode(OptionTypeA.self))
case .b: self = .typeB(try typeContainer.decode(OptionTypeB.self))
}
}
}
struct OptionTypeA: Decodable {
let label: String
let value: String
// and some others...
}
struct OptionTypeB: Decodable {
let label: String
let value: String
// and some others...
}
let jsonString = """
{
"options": [
{
"objectType": "OptionTypeA",
"label": "optionALabel1",
"value": "optionAValue1"
},
{
"objectType": "OptionTypeB",
"label": "optionBLabel",
"value": "optionBValue"
},
{
"objectType": "OptionTypeA",
"label": "optionALabel2",
"value": "optionAValue2"
}
]
}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
for option in result.options {
switch option {
case .typeA(let optionTypeA): print(optionTypeA)
case .typeB(let optionTypeB): print(optionTypeB)
}
}
} catch {
print(error)
}

How do I decode and encode JSON into a generic struct in Swift

Say the JSON looks like this:
{
"users": [
{
"id": 6,
"email": "123#gmail.com"
},
{
"id": 2,
"email": "345#gmail.com"
}
],
"meta": {
"current_page": 1,
"next_page": 2,
"prev_page": null,
"total_pages": 3,
"total_count": 12
}
}
sometime it could look like this
{
"messages": [
{
"id": 6,
"text": "hello"
},
{
"id": 2,
"text": "hi"
}
],
"meta": {
"current_page": 1,
"next_page": 2,
"prev_page": null,
"total_pages": 3,
"total_count": 12
}
}
As you can see, the codingkey will change according to the object in the JSON.
How do I parse this JSON into something that I can read and is dynamic, like:
struct GenericListModel<ListObject: Codable>: Codable {
let list: [ListObject]
let page: PaginationModel
}
Where I will create the ListObject separately e.g: UserModel.
I will then create the model:
GenericListModel<UserModel>(list: UserModel(id: 6, email: "123#gmail.com"), page: PaginationModel())
You may want to tweak it a little but you can do something like this:
struct Metadata: Codable {
// ...
}
struct User: Codable {
let id: Int
let email: String
}
struct Message: Codable {
// ...
}
protocol ListKeyable: CodingKey {
static var listKey: Self { get }
}
enum UserKeys: String, ListKeyable {
case users
static var listKey: UserKeys { .users }
}
enum MessageKeys: String, ListKeyable {
case messages
static var listKey: MessageKeys { .messages }
}
class PagedList<Element: Codable, ListKeys: ListKeyable>: Codable {
enum MetaKeys: String, CodingKey {
case meta
}
let list: [Element]
let meta: Metadata
func encode(to encoder: Encoder) throws {
var container1 = encoder.container(keyedBy: ListKeys.self)
var container2 = encoder.container(keyedBy: MetaKeys.self)
try container1.encode(list, forKey: ListKeys.listKey)
try container2.encode(meta, forKey: .meta)
}
required init(from decoder: Decoder) throws {
let container1 = try decoder.container(keyedBy: ListKeys.self)
let container2 = try decoder.container(keyedBy: MetaKeys.self)
list = try container1.decode([Element].self, forKey: ListKeys.listKey)
meta = try container2.decode(Metadata.self, forKey: .meta)
}
}
let decoder = JSONDecoder()
let list = try decoder.decode(PagedList<User, UserKeys>.self, from: users)

How to map this heterogeneous object with the model in swift?

Okay, so I am stuck at decoding the last item of this particular json by this the model, "payload" is always nil, Inside this "payload" object I can make my own json structure, I am able to decode the "text" but when it comes to the last item which is "payload", it is not working and is always nil.
I am not using any third-party library.
My Model Class.
struct DailougeFlowModel : Decodable {
// private enum CodingKeys : String, CodingKey {
// case responseId = "responseId"
// case queryResult = "queryResult"
// }
let responseId : String?
let queryResult : QueryResult?
}
struct QueryResult: Decodable {
// private enum CodingKeys : String, CodingKey {
// case fulfillmentText = "fulfillmentText"
// case fulfillmentMessages = "fulfillmentMessages"
// }
let fulfillmentText : String?
let fulfillmentMessages : [FulfillmentMessages]?
}
struct FulfillmentMessages: Decodable {
let text : TextModel?
let payLoad : Questions?
}
struct TextModel: Decodable {
let text : [String]?
}
struct Questions : Decodable{
let questions : [String]?
}
This json is what I am getting from the dailogeflow(V2). I am integrating a chatbot in the application.
{
"responseId": "2b879f78-cc05-4735-a7e8-067fdb53a81d-f6406966",
"queryResult": {
"fulfillmentMessages": [
{
"text": {
"text": [
"Text Here"
]
}
},
{
"text": {
"text": [
"Another Reply For Hi"
]
}
},
{
"payload": {
"questions": [
"Question One",
"Question Two",
"Question Three",
"Question Four"
]
}
}
]
}
}
Specify the inner model names as it is in the json response, if you want to specify your own model name then you would need to set an enum in each model just like the first model 'ResponseModel'
// MARK: - ResponseModel
struct ResponseModel: Codable {
let responseID: String
let queryResult: QueryResult
enum CodingKeys: String, CodingKey {
case responseID = "responseId"
case queryResult
}
}
// MARK: - QueryResult
struct QueryResult: Codable {
let fulfillmentMessages: [FulfillmentMessage]
}
// MARK: - FulfillmentMessage
struct FulfillmentMessage: Codable {
let text: Text?
let payload: Payload?
}
// MARK: - Payload
struct Payload: Codable {
let questions: [String]
}
// MARK: - Text
struct Text: Codable {
let text: [String]
}