I have a json file that looks like this (in a file called list.json)
[
{
"id": "C8B046E9-70F5-40D4-B19A-40B3E0E0877B",
"name": "Dune",
"author": "Frank Herbert",
"page": "77",
"total": "420",
"image": "image1.jpg"
},
{
"id": "2E27CA7C-ED1A-48C2-9B01-A122038EB67A",
"name": "Ready Player One",
"author": "Ernest Cline",
"page": "234",
"total": "420",
"image": "image1.jpg"
}
]
This a default file that comes with my app (These are examples that can be deleted). My content view has a member variable that uses a decode function I wrote to get the json array and display it in a list. I have a view to add another book to the json file. The view appends another struct to the array and then encodes the new appended array to list.json with this function
func writeJSON(_ bookData: [Book]) {
do {
let fileURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("list.json")
let encoder = JSONEncoder()
try encoder.encode(bookData).write(to: fileURL)
} catch {
print(error.localizedDescription)
}
}
This function is called in the NewBook view when a button is pressed. bookData is the decoded array in my content view which I used a Binding to in my NewBook view.
The code works if you add the book and go back to the contentview (the list now contains the appended struct) but if you close the app and open it again, the list uses the default json file. I think there is a mistake in my writeJSON function.
Also note that I tried changing the create parameter to false in the URL but that didn't help.
edit: I am adding the Book struct as requested
struct Book: Hashable, Codable, Identifiable {
var id: UUID
var name: String
var author: String
var page: String
var total: String
var image: String
}
edit 2: This is for an iOS app
edit 3: my load data function
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
You are probably not overriding the existing file on disk. Try options: .atomic
while writing the data to disk.
func writeJSON(_ bookData: [Book]) {
do {
let fileURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("list.json")
try JSONEncoder().encode(bookData).write(to: fileURL, options: .atomic)
} catch {
print(error)
}
}
edit/update:
The issue here is that you are not saving the file where you think it would. The Bundle directory is read-only and has no relation with your App documents directory.
func load<T: Decodable>(_ filename: String) -> T? {
// no problem to force unwrap here you actually do want it to crash if the file it is not inside your bundle
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")
// check if the file has been already copied from the Bundle to the documents directory
if !FileManager.default.fileExists(atPath: jsonURL.path) {
// if not copy it to the documents (not it is not a read-only anymore)
try? FileManager.default.copyItem(at: readURL, to: jsonURL)
}
// read your json from the documents directory to make sure you get the latest version
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")
// write your json at the documents directory and use atomic option to override any existing file
try? JSONEncoder().encode(bookData).write(to: jsonURL, options: .atomic)
}