Keep in mind that OrderedDictionary's Codable conformance extends to all Codable formats, and not just JSON. Using an UnkeyedEncodingContainer is entirely correct for maintaining the order of the dictionary, even if in certain formats it produces something that doesn't look like a dictionary; the key is that the runtime representation of the OrderedDictionary is the same.
For JSON specifically, the spec indeed places absolutely no restrictions on the meaning of key-value pairs in dictionaries — which means that in practice, most JSON parsers and encoders don't preserve order at all, because it usually requires more work to do so. This is at least partially due to the fact that in many languages, dictionaries are themselves unordered, and decoding is very often eager, rehydrating an entire payload into language-native collections. (This is part of the reason why, at the moment, JSONDecoder can't preserve ordering: because it uses JSONSerialization under the hood, it gets back fully-hydrated Dictionary values back, so ordering has already been lost by the time that JSONDecoder gets the data. This is something that may change with the upcoming swift-foundation work and rewrite.)
This means that if you're using JSON to communicate with any sort of service, you have to be very careful because can't really rely on "ordered dictionary " → service → "ordered dictionary" producing the same results. (Unless you encode the dictionary as an array, in which case it can't ever reasonably be reordered, unless intentionally.)
This is actually possible today, without changing the encoded representation of OrderedDictionary at all:
OrderedDictionary → JSON → OrderedDictionary is obviously possible
OrderedDictionary → JSON → Dictionary is already possible: Dictionary already knows* how to decode from an array of key-value pairs, in the same way that OrderedDictionary encodes
Dictionary → JSON → OrderedDictionary is also possible, by having OrderedDictionary.init(from:) check for a keyed container, and if one is present, decode from that (i.e., the reverse of what Dictionary does). The caveat here is that OrderedDictionary has no choice but to trust that the order of the keyed given by the keyed contain is the same as was in the original data, which currently is not the case (and in general, decoders for a variety of formats can't make that promise) — but for JSONDecoder specifically, could be
*The tricky thing is that at the moment, if you try to decode a dictionary keyed by either String or Int, Dictionary assumes that you must have a keyed container representation, so you'll get an error if you try to decode OrderedDictionary<String, ...> as Dictionary<String, ...>, but this can be loosened. You can also work around this by using a wrapper key type that encodes/decodes as a String (even a RawRepresentable type should work), so this does work:
import Foundation
import OrderedCollections
let orderedDict: OrderedDictionary = ["one": 1, "two": 2, "three": 3]
print(orderedDict) // => [one: 1, two: 2, three: 3]
let encoded = try! JSONEncoder().encode(orderedDict)
print(String(data: encoded, encoding: .utf8)!) // => ["one",1,"two",2,"three",3]
struct Key: RawRepresentable, Hashable, Decodable {
let rawValue: String
}
let decoded = try! JSONDecoder().decode([Key: Int].self, from: encoded)
print(decoded) // => [Key(rawValue: "three"): 3, Key(rawValue: "one"): 1, Key(rawValue: "two"): 2]
Also keep in mind that changing the encoded representation of a type isn't something you can do loosely (or in some cases, at all). Once the encoded representation of a type is public, that representation can be written out to disk and persisted indefinitely; changing the representation means that older software versions (the app/package/whatever), unless written in a very forward-thinking manner, will choke on the new data format.
The bar for breaking backwards compatibility in data serialization (in the general case, at least) is exceedingly high, because you risk throwing away support for otherwise perfectly valid software which can't necessarily be updated.