How to encode enum with Arrays of Custom Objects - swift

Thanks for your help in advance! I am relatively new to SwiftUI and have been struggling with encoding an enum with Arrays of custom objects. Here is the code:
struct ChartData: Codable {
var data: DataType
...
private enum CodingKeys : String, CodingKey { case id, title, type, info, label_x, label_y, last_update, data }
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
...
try container.encode(self.data, forKey: .data)
}
}
enum DataType: Codable{
case plot([ChartPlotData])
case slice([ChartSliceData])
case number([ChartNumberData])
}
struct ChartPlotData: Codable {
var chart_id: String
var order_plot: Int
var label_plot: String?
var points_x: [String]
var points_y: [Double]
private enum CodingKeys : String, CodingKey { case chart_id, order_plot, label_plot, points_x, points_y }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.chart_id = try container.decode(String.self, forKey: .chart_id)
self.order_plot = try container.decode(Int.self, forKey: .order_plot)
self.label_plot = try? container.decode(String.self, forKey: .label_plot)
do{
self.points_x = try container.decode([String].self, forKey:.points_x)
}catch{
let xs = try container.decode([Double].self, forKey:.points_x)
self.points_x = xs.map { String($0) }
}
self.points_y = try container.decode([Double].self, forKey:.points_y)
}
}
struct ChartSliceData: Codable {
var chart_id: String
var order_slice: Int
var label_slice: String
var value_slice: Double
}
struct ChartNumberData: Codable {
var chart_id: String
var number: Double
var unit: String?
}
I am attempting to cache JSON in UserDefaults so as to avoid having to make extraneous API calls. Using JSONEncoder, I am left with the following JSON snippet (excerpted from a much longer string):
"data" : {
"plot" : {
"_0" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
However, I am looking to get an encoding like this:
"data" : [
{
"label_plot" : "China",
"points_y" : [
0,
0,
0,
...
Any help would be greatly appreciated! Thanks so much!

This can be solved by adding an extra item in the CodingKeys enum for encoding and decoding the type used for the DataType enum and then using a switch to encode and decode the right type of array.
Here is the full ChartData struct although with some properties and code removed for brevity
struct ChartData: Codable {
var id: Int
var title: String
var data: DataType
enum CodingKeys: String, CodingKey {
case id, title, data, type
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
switch data {
case .number(let values):
try container.encode("number", forKey: .type)
try container.encode(values, forKey: .data)
case .plot(let values):
try container.encode("plot", forKey: .type)
try container.encode(values, forKey: .data)
case .slice(let values):
try container.encode("slice", forKey: .type)
try container.encode(values, forKey: .data)
}
}
init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "number":
let values = try container.decode([ChartNumberData].self, forKey: .data)
data = .number(values)
// case ...
default:
fatalError("Unsupported type for DataType: \(type)")
}
}
}

Related

Swift: How to automatically generate corresponding enum when use Codable

struct User: Codable {
var name: String
var createdAt: Date
var updatedAt: Date
var githubId: Int
}
extension User {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
self.updatedAt = try container.decode(Date.self, forKey: .updatedAt)
self.githubId = try container.decode(Int.self, forKey: .githubId)
}
}
when I called container.decode, the key is like a enum, which name corresponds to the property name, how to implement this feature.

Swift struct with custom encoder and decoder cannot conform to 'Encodable'

[Edited to provide a minimal reproducible example ]
This is the complete struct without non relevant vars and functions
InstrumentSet.swift
import Foundation
struct InstrumentsSet: Identifiable, Codable {
private enum CodingKeys: String, CodingKey {
case name = "setName"
case tracks = "instrumentsConfig"
}
var id: String { name }
var name: String
var tracks: [Track]
}
Track.swift
import Foundation
extension InstrumentsSet {
struct Track: Identifiable, Encodable {
private enum TrackKeys: String, CodingKey {
case id = "trackId"
case effects
}
let id: String
var effects: [Effect]?
}
}
extension InstrumentsSet.Track: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TrackKeys.self)
id = try container.decode(String.self, forKey: .id)
effects = try container.decodeIfPresent([Effect].self, forKey: .effects)
}
}
Effect.swift
import Foundation
import AudioKit
import SoundpipeAudioKit
extension InstrumentsSet.Track {
enum Effect: Decodable {
private enum EffectKeys: String, CodingKey {
case effectType = "effectName"
case cutoffFrequency
case resonance
}
case lowPassFilter(LowPassFilterEffect)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: EffectKeys.self)
let effectType = try container.decode(Effect.EffectType.self, forKey: .effectType)
switch effectType {
case .lowPassFilter:
let cutOffFrequency = try container.decode(ValueAndRange.self, forKey: .cutoffFrequency)
let resonance = try container.decode(ValueAndRange.self, forKey: .resonance)
self = .lowPassFilter(LowPassFilterEffect(cutOffFrequency: cutOffFrequency, resonance: resonance))
default:
fatalError("Not implemented!")
}
}
}
}
extension InstrumentsSet.Track.Effect {
enum EffectType: String, Decodable {
case lowPassFilter
}
}
extension InstrumentsSet.Track.Effect: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
//FIXME: This is the location of the error: Type 'ValueAndRange.Type' cannot conform to 'Encodable'
try container.encode(ValueAndRange.self, forKey: .cutoffFrequency)
}
}
The problem is ValueAndRange.self not not conforming to Encodable
I've followed multiple examples to get to this implementation:
import Foundation
import AudioKit
struct ValueAndRange: Encodable {
private enum ValueRangeKeys: String, CodingKey {
case value
case range
}
static var zero: ValueAndRange { .init(value: 0, range: [0, 0]) }
var value: AUValue
var range: [Double]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ValueRangeKeys.self)
try container.encode(value, forKey: .value)
try container.encode(range, forKey: .range)
}
}
extension ValueAndRange: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ValueRangeKeys.self)
value = try container.decode(AUValue.self, forKey: .value)
range = try container.decode([Double].self, forKey: .range)
}
}
I cannot see why this struct should not conform to Encodable. Maybe any of you got betters eyes (and brains) then I got?
Your encode function is incorrectly trying to encode a type, ValueAndRange.self, rather than a value.
Looking at the init(from:) method I think your encode function should look something like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
switch self {
case .lowPassFilter(let effect):
try container.encode(effect.cutOffFrequency, forKey: .cutoffFrequency)
try container.encode(effect.resonance, forKey: .resonance)
}
}
I didn't include .effectType in this code since I am uncertain of its usage (isn't it always the same hard coded string?).

Decoding JSON in Swift to Dictionary with different types

I have a JSON result from the server that looks like the following:
let json = """
{
"type": "rating",
"data": {
"maxRating": 5,
"isDarkMode": true
}
}
"""
The value for data can be any key-values. I want to map this JSON to my Swift model. So, I implemented the following:
struct Model: Decodable {
let type: String
let data: [String: Any]
private enum CodingKeys: String, CodingKey {
case type
case data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(String.self, forKey: .type)
self.data = try container.decode([String: Any].self, forKey: .data) // THIS
}
}
But on self.data = try container.decode ... it gives me the following error:
error: no exact matches in call to instance method 'decode'
self.data = try container.decode([String: Any].self, forKey: .data)
How can I fix it?
[String : Any] is not decodable. You can not decode with Any. But there have other solutions. Here is one https://dev.to/absoftware/how-to-make-swift-s-string-any-decodable-5c6n
Make change in the Metadata as -
enum DataType: String, Codable {
case payload
case metadata
}
struct Payload: Codable {
let id: String
let eventName: String
let metadata: [Metadata]
}
struct Metadata: Codable {
let maxRating: Int?
let isDarkMode: Bool?
// Add other variables that may appear from your JSON
}
enum MyValue: Decodable {
case payload(_ payload: Payload)
case metadata(_ metadata: Metadata)
private enum CodingKeys: String, CodingKey {
case `type`
case `data`
}
init(from decoder: Decoder) throws {
let map = try decoder.container(keyedBy: CodingKeys.self)
let dataType = try map.decode(DataType.self, forKey: .type)
switch dataType {
case .payload:
self = .payload(try map.decode(Payload.self, forKey: .data))
case .metadata:
self = .metadata(try map.decode(Metadata.self, forKey: .data))
}
}
}
Use the following Extension
JSONCodingKeys.swift
import Foundation
// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a
struct JSONCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
extension KeyedDecodingContainer {
func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
return try container.decode(type)
}
func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
guard contains(key) else {
return nil
}
return try decode(type, forKey: key)
}
func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
var container = try self.nestedUnkeyedContainer(forKey: key)
return try container.decode(type)
}
func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
guard contains(key) else {
return nil
}
return try decode(type, forKey: key)
}
func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
var dictionary = Dictionary<String, Any>()
for key in allKeys {
if let boolValue = try? decode(Bool.self, forKey: key) {
dictionary[key.stringValue] = boolValue
} else if let stringValue = try? decode(String.self, forKey: key) {
dictionary[key.stringValue] = stringValue
} else if let intValue = try? decode(Int.self, forKey: key) {
dictionary[key.stringValue] = intValue
} else if let doubleValue = try? decode(Double.self, forKey: key) {
dictionary[key.stringValue] = doubleValue
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
dictionary[key.stringValue] = nestedDictionary
} else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
dictionary[key.stringValue] = nestedArray
}
}
return dictionary
}
}
extension UnkeyedDecodingContainer {
mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
var array: [Any] = []
while isAtEnd == false {
if let value = try? decode(Bool.self) {
array.append(value)
} else if let value = try? decode(Double.self) {
array.append(value)
} else if let value = try? decode(String.self) {
array.append(value)
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
array.append(nestedDictionary)
} else if let nestedArray = try? decode(Array<Any>.self) {
array.append(nestedArray)
}
}
return array
}
mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
return try nestedContainer.decode(type)
}
}
How to use.
struct Model: Decodable {
let type: String
let data: [String: Any]
private enum CodingKeys: String, CodingKey {
case type
case data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(String.self, forKey: .type)
self.data = try container.decode([String: Any].self, forKey: .data)
}
}
let decoder = JSONDecoder()
let userJson = """
{
"type": "rating",
"data": {
"maxRating": 5,
"isDarkMode": true
}
}
""".data(using: .utf8)!
let user = try! decoder.decode(Model.self, from: userJson)
print(user)
Original answer from
You have to create different struct for data parameter.
You can refer below struct for reference
import Foundation
struct JsonResponse : Codable {
let type : String?
let data : Data?
enum CodingKeys: String, CodingKey {
case type = "type"
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decodeIfPresent(String.self, forKey: .type)
data = try values.decodeIfPresent(Data.self, forKey: .data)
}
}
struct Data : Codable {
let maxRating : Int?
let isDarkMode : Bool?
enum CodingKeys: String, CodingKey {
case maxRating = "maxRating"
case isDarkMode = "isDarkMode"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
maxRating = try values.decodeIfPresent(Int.self, forKey: .maxRating)
isDarkMode = try values.decodeIfPresent(Bool.self, forKey: .isDarkMode)
}
}

Swift Recursively Encode a Custom Class Data Structure

I want to save a queue in UserDefault. When I decode the Queue from UserDefault, head and tail of the Queue are assigned by value which means they are two completely different objects and I can't enqueue through the tail. Is it possible to encode tail as a reference to head?
This is how class Queue is implemented
#State var RecentEmoji = Queue()
public class Node: Codable {
var value: Int
var next: Node?
init(value: Int) {
self.value = value
self.next = nil
}
enum CodingKeys: CodingKey {
case value, next
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
try container.encode(next, forKey: .next)
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
value = try container.decode(Int.self, forKey: .value)
next = try container.decode(Node?.self, forKey: .next)
}
}
public class Queue: Sequence, Codable {
fileprivate var head: Node?
private var tail: Node?
private var length: Int
init() {
head = nil
tail = nil
length = 0
}
enum CodingKeys: CodingKey {
case head, tail, length
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(head, forKey: .head)
try container.encode(tail, forKey: .tail)
try container.encode(length, forKey: .length)
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
head = try container.decode(Node?.self, forKey: .head)
tail = try container.decode(Node?.self, forKey: .tail)
length = try container.decode(Int.self, forKey: .length)
}
}
This is how I save the Queue to userDefault
let encoder = JSONEncoder()
do {
let encoded = try encoder.encode(RecentEmoji)
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "RecentEmoji")
} catch {
print("\(error)")
}
And this is how I get them back
if let savedEmoji = UserDefaults.standard.value(forKey: "RecentEmoji") as? Data {
let decoder = JSONDecoder()
if let recentEmoji = try? decoder.decode(Queue.self, from: savedEmoji) {
RecentEmoji = recentEmoji
}
}

Ambiguous reference to member 'decodeIfPresent(_:forKey:)

Is there anyway to make codable from [String: Any]?
I'm using SwiftyJSON and looks like the encode part does not give any error
struct MyClass: Codable {
var my_label: [String: Any]?
enum CodingKeys: String, CodingKey {
case my_label = "my_label"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
my_label = try values.decodeIfPresent([String: Any].self, forKey: .my_label)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(JSON(my_label as Any), forKey: .my_label)
}
}
some of the example show my_label will look like this, though there's no guarantee that's why it is Any
"my_label": {
"condition": {
"IsOpen": "Yes"
},
"label": "Test Completed:"
},
You can use JSONSerialization but with Decodable Any can't be used
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
if let res = json["my_label"] as? [String:Any] {
print(res)
}
} catch {
print(error)
}
Or
struct Root: Codable {
let myLabel: MyLabel
enum CodingKeys: String, CodingKey {
case myLabel = "my_label"
}
}
struct MyLabel: Codable {
let condition: Condition
let label: String
}
struct Condition: Codable {
let isOpen: String
enum CodingKeys: String, CodingKey {
case isOpen = "IsOpen"
}
}