Why does Int enum as dictionary key, produce different json string than Int as dictionary key?

Viewed 109

I try to convert a dictionary, with Int enum

enum TypeE: Int, Codable
{
    case note = 1
    case tab
}

let encoder = JSONEncoder()

let dictionary0 = [TypeE.note:"VALUE0", TypeE.tab:"VALUE1"]
var data = try encoder.encode(dictionary0)
var string = String(data: data, encoding: .utf8)!
// [1,"VALUE0",2,"VALUE1"]
print(string)

The produced json string output is

[1,"VALUE0",2,"VALUE1"]

It looks strange to me. As, the produced json string represents an array.


If I tested with

let encoder = JSONEncoder()

let dictionary1 = [1:"VALUE0", 2:"VALUE1"]
var data = try encoder.encode(dictionary1)
var string = String(data: data, encoding: .utf8)!
// {"1":"VALUE0","2":"VALUE1"}
print(string)

The produced json string output is

{"1":"VALUE0","2":"VALUE1"}

It seems like if I use Int enum as dictionary key, the produced json string will become representation of array?

Is there any mistake in my code, or my expectation is incorrect?

2 Answers

The source code of Codable provides explanation for this behavior. If the key isn't a String or an Int, the resulting type is an Array:

  public func encode(to encoder: Encoder) throws {
    if Key.self == String.self {
      // Since the keys are already Strings, we can use them as keys directly.
      ...
    } else if Key.self == Int.self {
      // Since the keys are already Ints, we can use them as keys directly.
      ...
    } else {
      // Keys are Encodable but not Strings or Ints, so we cannot arbitrarily
      // convert to keys. We can encode as an array of alternating key-value
      // pairs, though.
      var container = encoder.unkeyedContainer()
      for (key, value) in self {
        try container.encode(key)
        try container.encode(value)
      }
    }
  }

I made an extension for your case may be useful:

extension Dictionary where Key == TypeE.RawValue, Value == String {
    
    init(dictionaryLiteral elements: (TypeE, String)...) {
        var headers: [TypeE.RawValue: String] = [:]
        for pair in elements {
            headers[pair.0.rawValue] = pair.1
        }
        
        self = headers
    }
    
    subscript(typeE t: TypeE) -> Value? {
        get {
            return self[t.rawValue]
        }
        set {
            self[t.rawValue] = newValue
        }
    }
}

usage:

var dic2 = Dictionary<TypeE.RawValue, String>.init(dictionaryLiteral: (TypeE.note,"Str"), (TypeE.tab,"Str2"))

add new key/Value:

dic2[typeE: TypeE.thirdCase] = "Str3"

O/P: enter image description here

and get specific key:

enter image description here

Related