Updating a saved Codable struct to add a new property
As mentioned in the comments you can make the new property optional and then decoding will work for old data.
var newProperty: Int?
Another option is to apply a default value during the decoding if the property is missing.
This can be done by implementing init(from:)
in your struct
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
completedGame = try container.decode(Bool.self, forKey: .completedGame)
do {
newProperty = try container.decode(Int.self, forKey: .newProperty)
} catch {
newProperty = -1
}
levelScores = try container.decode([LevelScore].self, forKey: .levelScores)
}
This requires that you define a CodingKey
enum
enum CodingKeys: String, CodingKey {
case name, completedGame, newProperty, levelScores
}
A third option if you don't want it to be optional when used in the code is to wrap it in a computed non-optional property, again a default value is used. Here _newProperty
will be used in the stored json but newProperty
is used in the code
private var _newProperty: Int?
var newProperty: Int {
get {
_newProperty ?? -1
}
set {
_newProperty = newValue
}
}
Using Codable to set property values doesn't work through inheritance
The magic of Codable
relies on simplicity (using structs which don't support inheritance).
The more custom, the more code
You have to write a custom initializer in the subclass to consider inheritance (thanks to Hamish for the note that CodingKeys and initializer are synthesized in the base class), I omitted the Encodable
part intentionally
class Primary : Decodable {
var a: Int
/*
private enum CodingKeys: String, CodingKey { case a }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
a = try container.decode(Int.self, forKey: .a)
}
*/
}
class Secondary : Primary {
var b: Int
private enum CodingKeys: String, CodingKey { case b }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
b = try container.decode(Int.self, forKey: .b)
try super.init(from: decoder)
}
}
func testRef<T: Decodable>() throws -> T {
let json = "{\"a\":5, \"b\" : 10}".data(using: .utf8)!
return try JSONDecoder().decode(T.self, from: json)
}
do {
let secondary : Secondary = try testRef()
print(secondary.a, secondary.b) // 5 10
} catch { print(error) }
Codable custom class type property can not initialize its own properties from JSON
In Article.swift
try change the following lines
category = try Category(from: decoder)
main_image_urls = try Main_image_urls(from: decoder)
to
category = values.decodeIfPresent(Category.self, forKey: .category)
main_image_urls = values.decodeIfPresent(Main_image_urls.self, forKey: .main_image_urls)
Your class Main_image_urls
should also conform to codable.
You could also try to omit the init(from decoder: Decoder)
methods and let the compiler synthesize it. This also works when having a custom CodingKeys enum.
Update:
Also change the following line in Category.swift
parent_category = try Parent_category(from: decoder)
to
parent_category = values.decodeIfPresent(Parent_category.self, forKey: .parentCategory)
Setting or overwriting properties of generic codable objects
Remember, the generated Something.encode(to:)
method will call encoder.container(keyedBy: CodingKeys.self)
to get a container, and it will encode its properties into that container. So you need to make both object
and update
use the same container to encode themselves. Then maybe update
's properties will overwrite object
's properties. On the other hand, maybe the encoder will throw an error. Let's try it and see.
To make them use the same container, we'll create a wrapper type that holds the object and the update.
import Foundation
public struct Something : Codable
{
public var property1:String?
public var property2:String?
}
struct UpdateWrapper {
var something: Something
var update: Something
}
Now we will make UpdateWrapper
conform to Encodable
. If we let the compiler generate the conformance, it will look like this:
// What the compiler would generate, but NOT what we want.
extension UpdateWrapper: Encodable {
enum CodingKeys: String, CodingKey {
case something
case update
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(something, forKey: .something)
try container.encode(update, forKey: .update)
}
}
The reason we don't want this is because this will result in something
and update
getting separate containers from the encoder, and we want them to go into the same container. So instead we'll conform to Encodable
like this:
extension UpdateWrapper: Encodable {
func encode(to encoder: Encoder) throws {
try something.encode(to: encoder)
try update.encode(to: encoder)
}
}
In this way, something
and encoder
each receive the same Encoder
, with the same codingPath
, so they get the same container from the encoder. We can test it like this:
let original = Something(property1: "A", property2: "B")
let update = Something(property1: "C", property2: nil)
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(UpdateWrapper(something: original, update: update))
let jsonString = String(data: jsonData, encoding: .utf8)!
print("jsonString=\(jsonString)")
let decoder = JSONDecoder()
let reconstructed = try! decoder.decode(Something.self, from: jsonData)
print("reconstructed=\(reconstructed)")
Here's the output:
jsonString={"property2":"B","property1":"C"}
reconstructed=Something(property1: Optional("C"), property2: Optional("B"))
So it does work. Is it a good idea? I don't know. I'm not sure it's specified anywhere that you're actually allowed to encode with the same key into the same container twice.
How to add custom transient property in Codable struct
If you don't want to decode those 4 properties, just don't include them in the CodingKeys
(and don't forget to explicitly provide default values for them, so that the decoder can initialize the object properly):
struct VideoAlbum: Codable {
let id, image: String?
let video, mediaType: JSONNull?
let type, deleted, createdOn: String?
let modifiedOn: JSONNull?
var isPlaying: Bool? = nil
var currentTime: CMTime? = nil
var timeObserver: Any? = nil
var pausedByUser: Bool? = nil
enum CodingKeys: String, CodingKey {
// include only those that you want to decode/encode
case id, image, video
case mediaType = "media_type"
case type, deleted
case createdOn = "created_on"
case modifiedOn = "modified_on"
}
}
Decoding manually a non Codable property of a Codable class
Yes you can.
The error is that you're missing func encode(to encoder: Encoder) throws
in ClassA
. Codable = Encodable & Decodable
, so it's trying to find a way to encode ClassA
as well. ClassB
isn't encodable so it can't do it automatically, and you haven't told it how to do it manually either.
If you don't need to encode ClassA
instances, just make it Decodable
. Otherwise implement the missing encode
func.
Or just put in the work and make ClassB
codable as well. You can use an extension to add it after the fact. If you don't want to do that either, a workaround I've used is to declare a small private codable struct inside ClassA
like struct ClassBInfo: Codable
. Use that to get the info you need, then read its properties to init ClassB
.
Related Topics
Underlying Type for Tuple in Swift
Audiounit Callback and Synchronization: How to Ensure Thread Safety with Gcd
Uibutton Borders Function Only Gives Back White Borders
Why Is Swift Giving Me Inaccurate Floating Point Arithmetic Results
Swift Applications Takes More Space on Disk
Compiler Segmentation Fault While Using Set in Swift
Add New Card Is Not Being Called in Stripe Paymentoptionviewcontroller
Generic Function with Binary Operations
Delete Tableview Cell, and Remove Data from Firebase
Swift: Tvos Ibaction for UIcontrol in Collection View Cell Never Gets Called
Aurendercallbackstruct in Swift
Get Url from Open Dialog of Standard Swift Document-Based Application
How to Access Switch Results of a Case
How to Create a Custom Chain in Swift Combine
What's The Correct Number Type for Financial Variables in Swift
Why Does a Simple Swift Arithmetic Operation Compile So Slow