user17554243
user17554243

Reputation: 3

Why doesn't my struct encode in the order I declared properties and coding keys?

This is the main struct that encodes properly as far as when I print print(String(data: encoded, encoding: .utf8) as Any) the struct prints encoded with mainObject printing first then the rest of the variables in the struct but i want it to print: mainAccount, mainObject, reference in that order.

struct T: Codable {
    init(){}

     let mainAccount = "IMHOTECHPECOM"
     let mainObject = mainObject()
     let reference = UUID()
     // Enum that allows easy encoding
     enum CodingKeys: CodingKey {
         case mainAccount, mainObject, reference;
     }
     // function to conform to encodable protocol
     func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: CodingKeys.self)
         try container.encode(mainAccount, forKey: .merchantAccount)
         try container.encode(mainObject.self, forKey: .mainObject)
         try container.encode(reference, forKey: .reference)
       
     }
    
     // conforms with decodable protocol
     required init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         _ = try container.decode(String.self, forKey: .mainAccount)
         _ = try container.decode(String.self, forKey: .mainObject)
         _ = try container.decode(String.self, forKey: .reference)
         
     }
 }

This is the mainObject

struct mainObject:  Codable {
    var type = "kind"
    var identifier: String = ""
    var guide: String = ""
    
    init(){}
   
    enum CodingKeys: CodingKey{
         case type, identifier, guide;
    }
    // function to conform to encodable protocol
    func encode(to encoder: Encoder) throws {
        
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey: .type)
        try container.encode(identifier, forKey: .identifier)
        try container.encode(guide, forKey: .guide)
        }
    // conforms with decodable protocol
   required init(from decoder: Decoder) throws {
       let container = try decoder.container(keyedBy: CodingKeys.self)
       type = try container.decode(String.self, forKey: .type)
       identifier = try container.decode(String.self, forKey: .identifier)
       guide = try container.decode(String.self, forKey: .identifier) 
     
    }
}

This is the function in the actual view encoding the data from a button press

func getBalance() async {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.sortedKeys]
        encoder.outputFormatting = [.withoutEscapingSlashes]
        encoder.outputFormatting = [.prettyPrinted]
        
        guard let encoded = try? encoder.encode(mainobject) else {
            print("Failed to encode")
            return
        }
        
        let url = URL(string: "https://testurl.com")!
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        
        do {
            let (response, _) = try await URLSession.shared.upload(for: request, from: encoded)
            print(String(data: encoded, encoding: .utf8) as Any)
            print(String(data: response, encoding: .utf8) as Any)
            } catch {
            print("Encoding failed")
        }
  
        let _ = try? JSONDecoder().decode(mainObject.self, from: encoded)
     

Upvotes: 0

Views: 965

Answers (1)

rob mayoff
rob mayoff

Reputation: 386038

In this particular case, the order you're asking for happens to match alphabetical order. So if you add .sortedKeys to your encoder's outputFormatting property, you'll get the order you want:

let encoder = JSONEncoder() 
encoder.outputFormatting = [.sortedKeys]
let data = try encoder.encode(myT)

This will affect the order of keys in all objects in the JSON. So your T's keys will be output in the order mainAccount, mainObject, reference, and your mainObject's keys will be output in the order guide, identifier, type.

The general answer is that JSONEncoder doesn't remember the order in which you add keys to a keyed container. Internally, it uses a standard Swift Dictionary to store the keys and values. A Swift Dictionary doesn't guarantee any ordering of its keys, and the order can change each time your program is started.

If you want to guarantee that the order of your keys is preserved, you'll have to write your own Encoder implementation, which is not a trivial task.

Upvotes: 3

Related Questions