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

Itai Ferber iferber at apple.com
Thu Oct 19 12:40:28 CDT 2017


Why are you stuck? I think the following matches your needs, no?

```swift
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 
>> <mailto: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 <mailto: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 
>>> <mailto:geojay at gmail.com>> wrote:
>>>
>>>>
>>>> David Baraff via swift-users <swift-users at swift.org 
>>>> <mailto: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 <mailto:swift-users at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-users 
>>>> <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 
>> <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/4168ab54/attachment.html>


More information about the swift-users mailing list