Reputation: 3
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
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