Arnav Motwani
Arnav Motwani

Reputation: 817

Updating json model swiftUI

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

Answers (1)

Joakim Danielson
Joakim Danielson

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

Related Questions