Reputation: 817
I currently have a json file that stores data persistently in my app. The json data looks like this:
[
{
"id": "C8B046E9-70F5-40D4-B19A-40B3E0E0877B",
"name": "Dune",
"author": "Frank Herbert",
"genre": "Science fiction",
"page": "77",
"total": "420"
},
{
"id": "2E27CA7C-ED1A-48C2-9B01-A122038EB67A",
"name": "Ready Player One",
"author": "Ernest Cline",
"genre": "Science fiction",
"page": "234",
"total": "420"
}
]
It decodes from and encodes into an array of objects with the following global functions:
func load<T: Decodable>(_ filename: String) -> T {
let readURL = Bundle.main.url(forResource: filename, withExtension: "json")!
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let jsonURL = documentDirectory
.appendingPathComponent(filename)
.appendingPathExtension("json")
// the code takes my example books from my bundle if they are a new user
if !FileManager.default.fileExists(atPath: jsonURL.path) {
try? FileManager.default.copyItem(at: readURL, to: jsonURL)
}
return try! JSONDecoder().decode(T.self, from: Data(contentsOf: jsonURL))
}
func writeJSON(_ bookData: [Book]) {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let jsonURL = documentDirectory
.appendingPathComponent("list")
.appendingPathExtension("json")
try? JSONEncoder().encode(bookData).write(to: jsonURL, options: .atomic)
}
and here's the struct
struct Book: Hashable, Codable, Identifiable {
var id: UUID
var name: String
var author: String
var genre: String
var page: String
var total: String
}
I now want to add a new key to the data (for example another string). If I just update my struct and , the apps of existing users will crash considering their json files will be missing the new keys and jsondecoder won't be able to decode them. I load the data into my list with a variable in my view @State var bookData: [Book] = load("list")
How can I best update my json model? I tried adding a condition to the load function that takes the original data, adds the new keys and store it in a new file to load however I couldn't get it to work.
Upvotes: 1
Views: 322
Reputation: 51892
You can either use a default value for the new property and set it in a custom init
or make the new property optional. Below is an example of both
struct Book: Hashable, Codable, Identifiable {
var id: UUID
var name: String
var author: String
var genre: String
var page: String
var total: String
var newProp: String
var newOptionalProp: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
author = try container.decode(String.self, forKey: .author)
genre = try container.decode(String.self, forKey: .genre)
page = try container.decode(String.self, forKey: .page)
total = try container.decode(String.self, forKey: .total)
if let newPropValue = try container.decodeIfPresent(String.self, forKey: .newProp) {
newProp = newPropValue
} else {
newProp = "Some default value"
}
}
}
Here is an example
let data = """
{
"id": "C8B046E9-70F5-40D4-B19A-40B3E0E0877B",
"name": "Dune",
"author": "Frank Herbert",
"genre": "Science fiction",
"page": "77",
"total": "420"
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Book.self, from: data)
print(result)
} catch {
print(error)
}
Book(id: C8B046E9-70F5-40D4-B19A-40B3E0E0877B, name: "Dune", author: "Frank Herbert", genre: "Science fiction", page: "77", total: "420", newProp: "Some default value", newOptionalProp: nil)
Upvotes: 1