[swift-users] dealing with heterogenous lists/dictionary with Codable

Itai Ferber iferber at apple.com
Thu Oct 19 14:46:17 CDT 2017


Hi Geordie,

Yep, that’s the difference here — you can’t decode something with 
an existential type; it has to be concrete (like you’re doing).
The reason for this is that it’s simply not possible to determine what 
type to decode just from the structure of the payload.

Consider the following:

```swift
let json = """
{"name": "Itai", "email": "iferber at apple.com"}
""".data(using: .utf8)!

let thing = try decoder.decode(Codable.self, from: json)
```

What should the type of `thing` be? Is it `[String : String]` because 
that’s what’s in the JSON?
What if I expected it to be `Person`, which is defined as

```swift
struct Person : Codable {
     let name: String
     let email: String
}
```

There’s no difference whether the `Codable` thing is at the top level 
or in a dictionary — if I try to decode `[String : Codable]`, I would 
run into the same issue.
The type needs to be concrete so we can figure out what initializer to 
call; if you write your own type (like an `enum`, which is what is 
almost always appropriate in this case), you can supply that type as the 
concrete type to attempt.

— Itai

On 19 Oct 2017, at 12:38, Geordie Jay wrote:

> David Baraff <davidbaraff at gmail.com> schrieb am Do. 19. Okt. 2017 um 
> 21:35:
>
>>
>>
>> Begin forwarded message:
>>
>> From: Geordie Jay <geojay at gmail.com>
>>
>> Subject: Re: [swift-users] dealing with heterogenous lists/dictionary 
>> with
>> Codable
>>
>> Date: October 19, 2017 at 12:24:44 PM PDT
>>
>> To: David Baraff <davidbaraff at gmail.com>, Itai Ferber 
>> <iferber at apple.com>
>>
>> Cc: swift-users <swift-users at swift.org>
>>
>>
>>
>>
>> David Baraff <davidbaraff at gmail.com> schrieb am Do. 19. Okt. 2017 um
>> 21:14:
>>
>>> My apologies.  I misstated the problem: I don’t want to just limit 
>>> to
>>> Int, String, [Int], etc. but also allow structures where
>>>
>>> struct NewThingy : Codable {
>>> let data1: T1
>>> let data2: T2
>>> }
>>>
>>> where T1 and T2 are themselves Codable.
>>>
>>
>> This is already possible, just not with dictionaries of unknown types
>> (because they’re not known to be Codable)
>>
>>
>> Sure, but I don’t want to give a dictionary of unknown types: i’m 
>> very
>> happy to say that my dictionary is
>> [String : Codable]
>>
>> but
>> struct Foo : Codable {
>> let d: [String : Codable]
>> }
>>
>> doesn’t work; the d inside F is not itself Codable.
>>
>
> That’s strange. We’re actually doing exactly this and it works for 
> us
> (although we are using a concrete Codable type rather than the Codable
> metatype itself).
>
> Maybe it’s worth filing a bug on Jira
>
> Good luck.
>
>
>>
>>
>>> So basically, back to wanting to let the compiler do the work, when 
>>> I
>>> make new structures, while still allowing for heterogenous 
>>> containers.
>>>
>>
>> It’s also possible to give the compiler hints as to what decodes 
>> into
>> what. Have you looked at the docs on the Apple foundation page?
>>
>>
>> https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
>>
>> Geordie
>>
>>
>>
>>
>>>
>>>
>>>
>>>
>>> Begin forwarded message:
>>>
>>> From: Itai Ferber <iferber at apple.com>
>>>
>>> Subject: Re: [swift-users] dealing with heterogenous 
>>> lists/dictionary
>>> with Codable
>>>
>>> Date: October 19, 2017 at 10:40:28 AM PDT
>>>
>>> To: David Baraff <davidbaraff at gmail.com>
>>>
>>> Cc: Geordie Jay <geojay at gmail.com>, swift-users 
>>> <swift-users at swift.org>
>>>
>>>
>>> Why are you stuck? I think the following matches your needs, no?
>>>
>>> import Foundation
>>> enum MyType : Codable, Equatable {
>>>     case int(Int)
>>>     case string(String)
>>>     case list([MyType])
>>>     case dictionary([String : MyType])
>>>
>>>     public init(from decoder: Decoder) throws {
>>>         // Can be made prettier, but as a simple example:
>>>         let container = try decoder.singleValueContainer()
>>>         do {
>>>             self = .int(try container.decode(Int.self))
>>>         } catch DecodingError.typeMismatch {
>>>             do {
>>>                 self = .string(try container.decode(String.self))
>>>             } catch DecodingError.typeMismatch {
>>>                 do {
>>>                     self = .list(try 
>>> container.decode([MyType].self))
>>>                 } catch DecodingError.typeMismatch {
>>>                     self = .dictionary(try container.decode([String 
>>> : MyType].self))
>>>                 }
>>>             }
>>>         }
>>>     }
>>>
>>>     public func encode(to encoder: Encoder) throws {
>>>         var container = encoder.singleValueContainer()
>>>         switch self {
>>>         case .int(let int): try container.encode(int)
>>>         case .string(let string): try container.encode(string)
>>>         case .list(let list): try container.encode(list)
>>>         case .dictionary(let dictionary): try 
>>> container.encode(dictionary)
>>>         }
>>>     }
>>>
>>>     static func ==(_ lhs: MyType, _ rhs: MyType) -> Bool {
>>>         switch (lhs, rhs) {
>>>         case (.int(let int1), .int(let int2)): return int1 == int2
>>>         case (.string(let string1), .string(let string2)): return 
>>> string1 == string2
>>>         case (.list(let list1), .list(let list2)): return list1 == 
>>> list2
>>>         case (.dictionary(let dict1), .dictionary(let dict2)): 
>>> return dict1 == dict2
>>>         default: return false
>>>         }
>>>     }
>>> }
>>> let values: MyType = .list([.int(42), .string("hello!"), 
>>> .list([.int(9), .string("hi")]), .dictionary(["zero": .int(0), 
>>> "one": .int(1)])])print(values)
>>> let encoder = JSONEncoder()let data = try 
>>> encoder.encode(values)print(String(data: data, encoding: .utf8)!) // 
>>> => [42,"hello!",[9,"hi"],{"zero":0,"one":1}]
>>> let decoder = JSONDecoder()let decoded = try 
>>> decoder.decode(MyType.self, from: data)print(decoded)
>>> print(values == decoded) // => true
>>>
>>> On 19 Oct 2017, at 20:15, David Baraff wrote:
>>>
>>> Begin forwarded message:
>>>
>>> From: Itai Ferber <iferber at apple.com>
>>>
>>> Subject: Re: [swift-users] dealing with heterogenous 
>>> lists/dictionary
>>> with Codable
>>>
>>> Date: October 19, 2017 at 9:39:25 AM PDT
>>>
>>> To: David Baraff <davidbaraff at gmail.com>
>>>
>>> Cc: Geordie Jay <geojay at gmail.com>, swift-users 
>>> <swift-users at swift.org>
>>>
>>>
>>> Hi David and Geordie,
>>>
>>> That approach won’t work — encoders and decoders only work 
>>> directly with
>>> concrete Codable types (e.g. String, Int, MyFoo [where MyFoo is 
>>> Codable],
>>> etc.).
>>> This is by design: since there is no type information stored in the 
>>> JSON
>>> payload, there isn’t necessarily a way to tell how to decode the 
>>> type
>>> you’re looking at, so asking for a generalCodable` isn’t 
>>> helpful.
>>>
>>> Since it’s unlikely that what you truly need is a [String : Any] 
>>> but
>>> really a [String : <one of String, Int, MyFoo, etc.>], one easy way 
>>> to
>>> decode this type is to create a wrapper enum or similar which 
>>> overrides
>>> init(from:) to be able to decode from one of those types. You can 
>>> then
>>> ask to decode a [String : MyWrapperType] and use that instead.
>>>
>>> What types are you expecting in the dictionary?
>>>
>>>
>>> The problem is that I want to be able to encode types T where
>>> (a) T is String, Int
>>> (b) lists of T
>>> (c ) dictionaries of type <String, T>
>>>
>>> The problem is the recursive nature: yes, my types are simple (say 
>>> only
>>> base types String and Int) but the “nesting” level may be quite 
>>> deep (a
>>> list of list of dictionaries of <etc.).
>>>
>>>
>>> Let’s turn this around:  in addition to the JSONEncoder, one can 
>>> also use
>>> the PropertyListEncoder.
>>>
>>> Are we saying that something one could pull from a property list 
>>> file
>>> (which is pretty much what i want: arbitrary deep nesting of basic 
>>> types)
>>> is also not Codable?  So a PropertyListEncoder could not encode 
>>> actual
>>> property lists?
>>>
>>> I really do want a heterogenous container.  I think I am stuck.
>>>
>>> — Itai
>>>
>>> On 19 Oct 2017, at 18:11, David Baraff via swift-users wrote:
>>>
>>> I’ll try.  Is that cast smart enough to apply recursively? We 
>>> shall see.
>>>
>>> Sent from my iPad
>>>
>>> On Oct 19, 2017, at 7:34 AM, Geordie Jay <geojay at gmail.com> wrote:
>>>
>>> I mean can you do something along the lines of
>>>
>>> let codableDict = stringAnyDict as? [String : Codable]
>>>
>>> ?
>>>
>>> I’m not at a computer to test it myself
>>>
>>>
>>>
>>>
>>> David Baraff <davidbaraff at gmail.com> schrieb am Do. 19. Okt. 2017 um
>>> 15:45:
>>>
>>>> That’s exactly what I want.  The ironic part is that I got my 
>>>> dictionary
>>>> by decoding a Json file.  If that’s where my dictionary came 
>>>> from, is there
>>>> a simple way of coercing the Json serialization routines to give me 
>>>> back
>>>> codables, rather than Anys?
>>>>
>>>>
>>>> Sent from my iPad
>>>>
>>>> On Oct 19, 2017, at 3:38 AM, Geordie Jay <geojay at gmail.com> wrote:
>>>>
>>>>
>>>> David Baraff via swift-users <swift-users at swift.org> schrieb am Do. 
>>>> 19.
>>>> Okt. 2017 um 03:47:
>>>>
>>>>> So I have simple structs like this:
>>>>>
>>>>>         struct Library: Codable {
>>>>>                 let domain: String
>>>>>                 let unit: String
>>>>>         }
>>>>>
>>>>> and it’s super-simple to serialize.  Yay.
>>>>>
>>>>> But:
>>>>>
>>>>>         struct LibraryGroup : Codable {         // I wish...
>>>>>            let libraries: [Library]
>>>>>            let someDict: [String : Any]
>>>>>         }
>>>>>
>>>>
>>>> I haven’t tried this, but is it possible to have a dictionary of 
>>>> [String
>>>> : Codable] ? Because that’s exactly the type requirements 
>>>> you’re
>>>> describing, no?
>>>>
>>>> Geordie
>>>>
>>>>
>>>>> So what I’m looking for is something where if the values in 
>>>>> someDict
>>>>> are themselves Codable, I can serialize things, and if they’re 
>>>>> not, I
>>>>> can’t.  In my previous scheme, I was using NSKeyedArchiver to 
>>>>> serialize
>>>>> everything, manualy, including someDict; in trying to switch to 
>>>>> Codable I
>>>>> ran smack into the fact that Codable wants to know what all the 
>>>>> types are,
>>>>> in advance.
>>>>>
>>>>> Am I just stuck?  How do I get the best of both worlds, where the
>>>>> compiler can make use of the fact that it can see the data types 
>>>>> of my
>>>>> structures, while still being able to serialize heterogenous data 
>>>>> like is
>>>>> found in LibraryGroup?
>>>>>
>>>>> Is my only alternative to write a custom coder for LibraryGroup?  
>>>>> Is
>>>>> there any hope I could teach Codable what to do with
>>>>>         [String: Any]
>>>>>
>>>>> ?
>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> swift-users mailing list
>>>>> swift-users at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-users
>>>>>
>>>> _______________________________________________
>>> swift-users mailing list
>>> swift-users at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-users
>>>
>>>


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20171019/8a3e404b/attachment.html>


More information about the swift-users mailing list