TransWikia.com

How do I custom encode to JSON a Swift object containing an array?

Stack Overflow Asked by Chatanooga on December 7, 2021

I am a newbie developer writing an iOS app that allows users to define and replay multiple ‘passages’ within a ‘piece’ of recorded music, multiple times for each passage.

My class ‘Piece’ is an Observable object which currently contains only the published var ‘passages’ (an array of records holding details of the passages the user wishes to replay). Although the only member of Piece is now a ‘passages’ array, I expect Piece will eventally have more members, which won’t be arrays.

I want to be able to save each Piece a user defines as a JSON file. Because ‘Piece’ is an observable object, it does not conform to Codable unless I write custom encode and decode functions. I have so far been unable to find any example code that illustrates how to encode an object (in my case, ‘Piece’) that, in turn, contains an array of custom objects (in my case, ‘Passage’) that have multiple properties.

The following shows the relevant parts of my code for ‘Piece’ and ‘Passage’.

class Piece: ObservableObject, Codable {

    @Published var passages = [Passage]()
    
    enum CodingKeys: CodingKey {
        case passages
    }
  
    init() { }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
    passages = try container.decode([Passage].self, forKey: .passages)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode([Passage](), forKey: .passages)
        
    }
}
 
struct Passage: Codable, Equatable, Identifiable, Hashable {

    var id = UUID()
    var startTime: Double
    var endTime: Double
    var extraPause: Double
    var numIterations: Int
    var skipMe: Bool = false
    
    enum PassageKeys: CodingKey {
        case id, startTime, endTime, extraPause, numIterations, skipMe
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: PassageKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(startTime, forKey: .startTime)
        try container.encode(endTime, forKey: .endTime)
        try container.encode(extraPause, forKey: .extraPause)
        try container.encode(numIterations, forKey: .numIterations)
        try container.encode(skipMe, forKey: .skipMe)
    }
}

In the interface of my app I have coded the following button action.

let url = self.getDocumentsDirectory().appendingPathComponent("X.json")
let encoder = JSONEncoder()
do {
    let data = try encoder.encode(self.piece)
        let string = String(data: data, encoding: .utf8)!
        print("Here is the string we are trying to write: (string)")
        try string.write(to: url, atomically: true, encoding: .utf8)
   } catch {
        print(error.localizedDescription)
}

When I populate the ‘passages’ array with several records then click the button that triggers this action, the console output reads:

Here is the string we are trying to write: {"passages":[]}

Setting a breakpoint inside the Passage.encode() function shows that this function is never called.

Can anyone help point me in the right direction?

One Answer

You should change

try container.encode([Passage](), forKey: .passages)

to

try container.encode(passages, forKey: .passages)

in your code. Otherwise you are always trying to encode new empty just created array [Passage]().

Answered by Chus on December 7, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP