swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.33k stars 10.34k forks source link

The JSONEncoder encode Dictionary to Array? #63778

Open Xezun opened 1 year ago

Xezun commented 1 year ago
struct Name: RawRepresentable, Codable, Hashable {
    typealias RawValue = String
    var rawValue: String
    init(rawValue: String) {
        self.rawValue = rawValue
    }
    var hashValue: Int {
        return rawValue.hashValue
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(String.self)
        self.init(rawValue: rawValue)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(rawValue)
    }
}

let value0 = Name.init(rawValue: "Name")
let encoder0 = JSONEncoder()
if let data = try? encoder0.encode(value0) {
    if let string = String(data: data, encoding: .utf8) {
        print(string) // ✅ prints: "Name"
    }
}

let value1 = [Name.init(rawValue: "Name"): "John"]
let encoder1 = JSONEncoder()
if let data = try? encoder1.encode(value1) {
    if let string = String(data: data, encoding: .utf8) {
        print(string) // ❌ prints: ["Name","John"]
    }
}

let value2 = ["Name": Name.init(rawValue: "John")]
let encoder2 = JSONEncoder()
if let data = try? encoder2.encode(value2) {
    if let string = String(data: data, encoding: .utf8) {
        print(string) // ✅ prints: {"Name":"John"}
    }
}

Environment

AnthonyLatsis commented 1 year ago

cc @parkera

parkera commented 1 year ago

We don’t have enough info when encoding the ”bad” example to know that Name will be encoded as a single value container with just a String and therefore is suitable for use as a key in a JSON dictionary (which is of course required to be a String). Therefore Dictionary goes into the fallback path of encoding itself as an Array instead.

Xezun commented 1 year ago

If a dictionary's key type is defined by OC like this:

typedef NSString *SomeKeyName NS_EXTENSIBLE_STRING_ENUM;

In swift, the key type was converted to struct, which lead to the JSONEncoder doesn't work as expected.

The JSONSerialization may work, but it needs all the value types implemented NSCoding protocol at first.

Is there any way to resolve this problem without changing many codes?

Thank you!

@parkera

miku1958 commented 8 months ago

I had the same problem, my goal was to use a string enum as a key so that I could better process the results, but failed due to the lack of this feature. I believe When the Decoder thinks it's a Dictionary, it should be able to get the target type, and instead of decoding the key with String, it should decode it with Dictionary.Key, like the Dictionary.Value

YOCKOW commented 8 months ago

Isn't this a duplicate of SR-7788? I think SE-0320 is a workaround for this.

miku1958 commented 8 months ago

@YOCKOW I just fixed this in swift-foundation(FoundationPreview), but it looks like I should move it to swift-corelibs-foundation?

miku1958 commented 8 months ago

SE-0320 is the workaround, adding CodingKeyRepresentable is working for me.