Swift Recursively Encode a Custom Class Data Structure - swift

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
}
}

Related

How to encode enum with Arrays of Custom Objects

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

Save array of different object types in UserDefaults

I have two classes, Radio and Podcast, children of the Media class.
I'm trying to save an array of Media (with radios and podcasts) to UserDefaults but when I get it back, I only have medias (I'm losing information of Radio or Podcast).
I cannot cast the items to Radio or Podcast.
private func saveRecentMediaInData(_ medias:[Media]) {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(medias) {
UserDefaults.standard.setValue(encoded, forKey: recentMediasKey)
}
}
private func getRecentMediasFromData() -> [Media] {
let defaults = UserDefaults.standard
if let data = defaults.value(forKey: recentMediasKey) as? Data {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Array.self, from: data) as [Media] {
return decoded
}
}
return []
}
Thanks
The issue is not related to UserDefaults. It's having an array of mixed object to decode with Codable.
In this case, a solution is to use a enum with associated value:
enum Mixed: Codable {
case radio(Radio)
case podcast(Podcast)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let asRadio = try? container.decode(Radio.self) {
self = .radio(asRadio)
} else if let asPodcast = try? container.decode(Podcast.self) {
self = .podcast(asPodcast)
} else {
fatalError("Oops")
}
}
}
Here is a full sample code:
struct SubClassesCodable {
class Media: Codable, CustomStringConvertible {
var title: String
var description: String {
return "Media: \(title)"
}
}
class Radio: Media {
var channel: Int
enum CodingKeys: String, CodingKey {
case channel
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.channel = try container.decode(Int.self, forKey: .channel)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(channel, forKey: .channel)
}
override var description: String {
return "Radio: \(title) - \(channel)"
}
}
class Podcast: Media {
var author: String
enum CodingKeys: String, CodingKey {
case author
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.author = try container.decode(String.self, forKey: .author)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(author, forKey: .author)
}
override var description: String {
return "Podcast: \(title) - \(author)"
}
}
enum Mixed: Codable {
case radio(Radio)
case podcast(Podcast)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let asRadio = try? container.decode(Radio.self) {
self = .radio(asRadio)
} else if let asPodcast = try? container.decode(Podcast.self) {
self = .podcast(asPodcast)
} else {
fatalError("Oops") //Or rather throws a custom error
}
}
}
static func test() {
let mediaJSONStr = #"{"title": "media"}"#
let radioJSONStr = #"{"title": "radio", "channel": 3}"#
let podcastJSONStr = #"{"title": "podcast", "author": "myself"}"#
do {
let decoder = JSONDecoder()
//Create values from JSON
let media = try decoder.decode(Media.self, from: Data(mediaJSONStr.utf8))
print(media)
let radio = try decoder.decode(Radio.self, from: Data(radioJSONStr.utf8))
print(radio)
let podcast = try decoder.decode(Podcast.self, from: Data(podcastJSONStr.utf8))
print(podcast)
let array: [Media] = [radio, podcast]
print(array)
// Encode to Data, that's what's saved into UserDefaults
let encoder = JSONEncoder()
let encodedArray = try encoder.encode(array)
print("Encoded: \(String(data: encodedArray, encoding: .utf8)!)") //It's more readable as JSON String than Data
//This will fail, it's the current author code
let decoded = try decoder.decode([Media].self, from: encodedArray)
print(decoded)
decoded.forEach {
if let asRadio = $0 as? Radio {
print(asRadio)
}else if let asPodcast = $0 as? Podcast {
print(asPodcast)
} else {
print("Nop: \($0)")
}
}
//This is a working solution
let mixedDecoded = try decoder.decode([Mixed].self, from: encodedArray)
let decodedArray: [Media] = mixedDecoded.map {
switch $0 {
case .radio(let radio):
return radio
case .podcast(let podcast):
return podcast
}
}
print(decodedArray)
} catch {
print("Error: \(error)")
}
}
}
SubClassesCodable.test()

NSKeyedUnarchiver creates multiple instances of the same object references by others

I'm experimenting with NSKeyedArchiver because I'm looking into saving an object graph. However, there seems to be a problem with objects referencing the same instance of another object. Say, I have three classes: A, B and C that reference each other like this:
A --> B <-- C
A and C each have a reference to the same object B. When decoding this using NSKeyedUnarchiver, it simply creates multiple instances of B so that A and B do not reference the same object anymore.
Here's a full example:
import Foundation
class A: Codable {
var b: B
init(b: B) {
self.b = b
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.b = try container.decode(B.self, forKey: .b)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(b, forKey: .b)
}
private enum CodingKeys: String, CodingKey {
case b
}
}
class B: Codable {
init() {}
required init(from decoder: Decoder) throws {}
func encode(to encoder: Encoder) throws {}
}
class C: Codable {
var b: B
init(b: B) {
self.b = b
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.b = try container.decode(B.self, forKey: .b)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(b, forKey: .b)
}
private enum CodingKeys: String, CodingKey {
case b
}
}
class Store: Codable {
var a: A
var b: B
var c: C
init() {
b = B()
a = A(b: b)
c = C(b: b)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.a = try container.decode(A.self, forKey: .a)
self.b = try container.decode(B.self, forKey: .b)
self.c = try container.decode(C.self, forKey: .c)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(a, forKey: .a)
try container.encode(b, forKey: .b)
try container.encode(c, forKey: .c)
}
private enum CodingKeys: String, CodingKey {
case a, b, c
}
}
let store = Store()
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
try! archiver.encodeEncodable(store, forKey: NSKeyedArchiveRootObjectKey)
archiver.finishEncoding()
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: archiver.encodedData)
if let decodedStore = try! unarchiver.decodeTopLevelDecodable(Store.self, forKey: NSKeyedArchiveRootObjectKey) {
// Will print false
print(decodedStore.a.b === decodedStore.c.b)
}
Am I doing something wrong or does something like this simply not work? Or is my example flawed?
Thanks!

Polymorphically decode a sub-object of a KeyedDecodingContainer in Swift

The following code attempts to create a Codable type AnyBase that would allow you to encode and decode various subclasses of Base. As written, in fails, because it tries to lookup the type objects by a string code, using getTypeFromCode. But if you use the commented-out part instead with hard-coded types, it prints "Sub1", as desired.
Is there a way to adjust this to use something like getTypeFromCode and eliminate the hard-coded types from init?
class Base: Codable {
var foo: Int = 1
var codingTypeKey: String { fatalError("abstract") }
}
class Sub1: Base {
var sub1Prop: Int = 2
override var codingTypeKey: String { "sub1" }
enum CodingKeys: CodingKey {
case sub1Prop
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
sub1Prop = try c.decode(Int.self, forKey: .sub1Prop)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(sub1Prop, forKey: .sub1Prop)
try super.encode(to: encoder)
}
}
class Sub2: Base {
var sub2Prop: Int = 2
override var codingTypeKey: String { "sub2" }
}
func getTypeFromCode(_ typeCode: String) -> Base.Type {
if typeCode == "sub1" { return Sub1.self }
else if typeCode == "sub2" { return Sub2.self }
else { fatalError("bad type code") }
}
class AnyBase: Codable {
let base: Base
init(_ b: Base) { base = b }
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
/*if typeCode == "sub1" {
self.base = try c.decode(Sub1.self, forKey: .base)
} else if typeCode == "sub2" {
self.base = try c.decode(Sub2.self, forKey: .base)
} else {
fatalError("bad type code")
}*/
let typeObj = getTypeFromCode(typeCode)
self.base = try c.decode(typeObj, forKey: .base)
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(base.codingTypeKey, forKey: .type)
try c.encode(base, forKey: .base)
}
enum CodingKeys: CodingKey {
case base, type
}
}
// To to round-trip encode/decode and get the right object back...
let enc = JSONEncoder()
let dec = JSONDecoder()
let sub = Sub1()
let any = AnyBase(sub)
let data = try! enc.encode(any)
let str = String(data:data, encoding:.utf8)!
print(str)
let any2 = try! dec.decode(AnyBase.self, from: data)
print(type(of:any2.base))
Instead of getTypeFromCode (_:) method, you can create an enum BaseType like,
enum BaseType: String {
case sub1, sub2
var type: Base.Type {
switch self {
case .sub1: return Sub1.self
case .sub2: return Sub2.self
}
}
}
Now, in init(from:) get the BaseType using typeCode as rawValue, i.e.
class AnyBase: Codable {
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
if let baseType = BaseType(rawValue: typeCode) {
self.base = try c.decode(baseType.type, forKey: .base)
} else {
fatalError("bad type code")
}
}
//rest of the code...
}

How to save custom type in CoreData with Codable Protocol Swift

I am trying to save the custom object of type codable, In which I am able to store Int16 type. But for [Movie] type in Coredata its NSObject, Entity I have an attribute movie is of type Transformable.
Error: No 'decodeIfPresent' candidates produce the expected contextual
result type 'NSObject?'
How can save this custom type Array with Transformable type
class MovieResults: Results, Codable {
required convenience public init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext,
let entity = NSEntityDescription.entity(forEntityName: "Results", in: managedObjectContext) else {
fatalError("Failed to retrieve managed object context")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.page = try container.decodeIfPresent(Int16.self, forKey: .page) ?? 0
self.numberOfResults = try container.decodeIfPresent(Int16.self, forKey: .numberOfResults) ?? 0
self.numberOfPages = try container.decodeIfPresent(Int16.self, forKey: .numberOfPages) ?? 0
self.movies = try container.decodeIfPresent([Movie].self, forKey: .movies) ?? nil
}
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(page, forKey: .page)
try container.encode(numberOfResults, forKey: .numberOfResults)
try container.encode(numberOfPages, forKey: .numberOfPages)
try container.encode(movies, forKey: .movies)
}
private enum CodingKeys: String, CodingKey {
case page
case numberOfResults = "total_results"
case numberOfPages = "total_pages"
case movies = "results"
}
}
Movie Array is an custom attribute of type Transformable in CoreData
class Movies: Movie, Codable {
public func encode(to encoder: Encoder) throws {
}
required convenience init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Movie", in: managedObjectContext) else {
fatalError("Failed to decode User")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.identifier = try container.decodeIfPresent(Int16.self, forKey: .identifier) ?? 0
self.posterPath = try container.decodeIfPresent(String.self, forKey: .identifier)
self.backdrop = try container.decodeIfPresent(String.self, forKey: .identifier)
self.title = try container.decodeIfPresent(String.self, forKey: .identifier)
self.releaseDate = try container.decodeIfPresent(String.self, forKey: .identifier)
self.rating = try container.decodeIfPresent(Int16.self, forKey: .rating) ?? 0
self.overview = try container.decodeIfPresent(String.self, forKey: .identifier)
}
enum CodingKeys: String, CodingKey {
case identifier
case posterPath = "poster_path"
case backdrop = "backdrop_path"
case title
case releaseDate = "release_date"
case rating = "vote_average"
case overview
}
}
With this, it's working fine.
self.movies = try container.decodeIfPresent([Movies].self, forKey: .movies)! as NSObject
I am Inheriting NSManagedObject Class Is this Correct way. I tried
using the extension but it throws an error for initializers.?
public convenience init(from decoder: Decoder) throws
Initializer requirement 'init(from:)' can only be satisfied by a
'required' initializer in the definition of non-final class
'MovieResult'
I think this could be done in a more simpler way. Here is how you can give a try. Here is MovieResult class [It's good to have the name of the class in singular].
public class MovieResult: Codable {
public var stringVariable: String
public var intVariable: Int
public init(stringVariable: String, intVariable: Int) {
self.stringVariable = stringVariable
self.intVariable = intVariable
}
}
Here you can persist MovieResult in UserDefaults.
public var savedMovieResult: MovieResult? {
get {
let data = UserDefaults.standard.object(forKey: "savedMovieResultKey") as? Data
var movieResult: MovieResult?
if let data = data {
do {
movieResult = try PropertyListDecoder().decode(MovieResult.self, from: data)
} catch {
print(error)
}
}
return movieResult
} set {
do {
try UserDefaults.standard.set(PropertyListEncoder().encode(newValue), forKey: "savedMovieResultKey")
UserDefaults.standard.synchronize()
} catch {
print(error)
}
}
}