Enum associated value confusing - swift

When I try use func obj func, I get the error:
Cannot invoke 'obj' with an argument list of type '(message: (QueueAddable))'
I'm confused with Swift types.
Obj func used to get concrete type for decode.
protocol QueueAddable: Encodable {
var playlistsCollection:String? { get }
var playlists: [String]? { get }
}
struct Playlist: QueueAddable {
var playlistsCollection:String? {
return "id"
}
var playlists: [String]? {
return ["id", "id2"]
}
private enum CodingKeys:String,CodingKey {
case playlistsCollection
case playlists
}
public func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: CodingKeys.self)
try values.encode(playlistsCollection, forKey: Playlist.CodingKeys.playlistsCollection)
try values.encode(playlists, forKey: .playlists)
}
}
func obj<Q>(message: Q) where Q: QueueAddable {
let encoder = JSONEncoder()
let data = try! encoder.encode(message)
}
enum SomeEnum {
case playlist(QueueAddable)
func doSome() throws -> Data {
switch self {
case .playlist(let queueAddable):
let encoder = JSONEncoder()
// Error on the following line:
obj(message: queueAddable)
return Data()
}
}
}
let playlist = Playlist()
let data = try SomeEnum.playlist(playlist).doSome()

I think the problem is that the function expects a type and not a protocol. If you make the enum generic using a type that implements the protocol it will work.
Change the enum's first two lines like this:
enum SomeEnum<Q : QueueAddable> {
case playlist(Q)
I tested in the following playground:
import Foundation
protocol QueueAddable: Encodable {
var playlistsCollection:String? { get }
var playlists: [String]? { get }
}
struct Playlist: QueueAddable {
var playlistsCollection:String? {
return "id"
}
var playlists: [String]? {
return ["id", "id2"]
}
private enum CodingKeys:String,CodingKey {
case playlistsCollection
case playlists
}
public func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: CodingKeys.self)
try values.encode(playlistsCollection, forKey: Playlist.CodingKeys.playlistsCollection)
try values.encode(playlists, forKey: .playlists)
}
}
func obj<Q>(message: Q) where Q: QueueAddable {
let encoder = JSONEncoder()
let data = try! encoder.encode(message)
}
enum SomeEnum<Q : QueueAddable> {
case playlist(Q)
func doSome() throws -> Data {
switch self {
case .playlist(let queueAddable):
let encoder = JSONEncoder()
// No longer error on the following line:
obj(message: queueAddable)
return Data()
}
}
}
let playlist = Playlist()
let data = try SomeEnum.playlist(playlist).doSome()
Hope it helps!

Related

how do you set nil to codable enum when unexpected value in swift iOS

I'm using Codable for my WebRequest response which is returning some predefined string or number. So, I'm using Enums for those. But when some unexpected value arrive to Response at that my Codable fails to decode.
Here some code for better understanding.
class WebUser: Codable, Equatable {
static func == (lhs: WebUser, rhs: WebUser) -> Bool {
return lhs.id == rhs.id
}
...
var mobileNumberPrivacy: CommonPrivacyOption?
var emailPrivacy: CommonPrivacyOption?
var dobPrivacy: CommonPrivacyOption?
...
}
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
//Does not help this optional init function
/*init?(rawValue: Int) {
switch rawValue {
case 1: self = .privacyOnlyMe
case 2: self = .privacyPublic
case 3: self = .privacyFriends
case 4: self = .privacyFriendsOfFriends
default: return nil
}
}*/
}
but sometimes from WebServer I'm getting, 0 for dobPrivacy at that time I'm getting DecodingError.dataCorrupted exception with context Cannot initialize CommonPrivacyOption from invalid Int value 0
As I expect to dobPrivacy nil when I get other values then 1/2/3/4.
EDIT:
let dict1 = [
"id": 2,
"mobileNumberPrivacy": 3,
"emailPrivacy": 4,
"dobPrivacy": 0 // Works perfectly with 1
]
do {
let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
print(context.codingPath)
print(context.debugDescription)
}
catch {
print(error.localizedDescription)
}
I'm using this same Codable WebUser object for Profile detail, search users and many more.
so may be some times one more key will not present in WebRequest's response.
I recommend writing a property wrapper that handles this problem for you.
Specifically, let's write a property wrapper named NilOnDecodingError that turns any DecodingError into nil.
Here's the declaration of NilOnDecodingError:
#propertyWrapper
public struct NilOnDecodingError<Wrapped> {
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: Wrapped?
}
We've defined it to wrap any type, storing an Optional.
Now we can conform it to Decodable when the Wrapped type is Decodable:
extension NilOnDecodingError: Decodable where Wrapped: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
wrappedValue = .some(try container.decode(Wrapped.self))
} catch is DecodingError {
wrappedValue = nil
}
}
}
We probably also want it to be Encodable when the Wrapped type is Encodable:
extension NilOnDecodingError: Encodable where Wrapped: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let value = wrappedValue {
try container.encode(value)
} else {
try container.encodeNil()
}
}
}
Now we can wrap the appropriate fields of WebUser:
class WebUser: Codable {
let id: String
#NilOnDecodingError
var mobileNumberPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var emailPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var dobPrivacy: CommonPrivacyOption?
}
For testing, we'll want to print the fields of the decoded user:
extension WebUser: CustomStringConvertible {
var description: String {
return """
WebUser(
id: \(id),
mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
"""
}
}
Now we can try it out:
let json = """
{
"id": "mrugesh",
"mobileNumberPrivacy": 1,
"emailPrivacy": 2,
"dobPrivacy": 1000
}
"""
let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Output:
WebUser(
id: mrugesh,
mobileNumberPrivacy: privacyOnlyMe,
emailPrivacy: privacyPublic),
dobPrivacy: nil))
You need to create a custom Decodable initializer inside WebUser:
class WebUser: Codable {
var dobPrivacy: CommonPrivacyOption?
// Rest of your properties here.
enum CodingKeys: String, CodingKey {
case dobPrivacy
// Add a case for each property you want to decode here.
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Use optional try to decode your enum so that when the
// decode fails because of wrong Int value, it will assign nil.
dobPrivacy = try? container.decode(CommonPrivacyOption.self, forKey: .dobPrivacy)
}
}
Alternatively, you can implement the Decodable initializer inside CommonPrivacyOption and add an additional case unknown like so:
enum CommonPrivacyOption: Int, Codable {
case privacyOnlyMe = 1
case privacyPublic, privacyFriends, privacyFriendsOfFriends
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
// Try to initialize Self from value, if
// value is not 1, 2, 3, or 4, initialize Self to
// the unknown case.
self = .init(rawValue: value) ?? .unknown
}
}
It looks to me like the compiler selects the wrong init for the enum types, instead of init(rawValue) it uses init(from:) that is the one for decoding (which in a way makes sense)
Here is a solution where we override this behaviour by using a custom init(from) in WebUser that decodes the raw values and then creates an enum item
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
self.mobileNumberPrivacy = mobileNumberPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
self.emailPrivacy = emailPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
self.dobPrivacy = dobPrivacy
}
}
Below is a small example
extension WebUser: CustomStringConvertible {
var description: String {
"Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
}
}
let data = """
{
"mobileNumberPrivacy": 1,
"dobPrivacy": 0
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(WebUser.self, from: data)
print(result)
} catch {
print(error)
}
Mobile: Optional(1), email: nil, dob: nil
Of course if you can change your mind about 0 being translated to nil then I would suggest you extend the enum to support the 0 value
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case none = 0
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Then it should work out of the box and you don't need to write any custom code.
I liked #alobaili's answer since it's simple and provided a good solution. One thing I wanted to improve is to make it more generic, so that any Decodable (and Codable) could do it with less code to write.
extension Decodable where Self: RawRepresentable, RawValue: Decodable {
static func initializedOptionalWith(decoder: Decoder, defaultValue: Self) throws -> Self {
let container = try decoder.singleValueContainer()
let value = try container.decode(RawValue.self)
return .init(rawValue: value) ?? defaultValue
}
}
Usage:
init(from decoder: Decoder) throws {
self = try .initializedOptionalWith(decoder: decoder, defaultValue: .unknown)
}
Thank you Rob Mayoff for a great answer. I would like to add just one thing - if the property is missing, decoding fails even if the property is optional. To prevent this failure, add the following extension:
extension KeyedDecodingContainer {
func decode<T>(_ type: NilOnDecodingError<T>.Type, forKey key: Self.Key) throws -> NilOnDecodingError<T> where T: Decodable {
try decodeIfPresent(type, forKey: key) ?? NilOnDecodingError<T>(wrappedValue: nil)
}
}

Skipping empty string with JSONEncoders swift

I a codable serialization extension which I use to turn my Codable struct to dictionaries, the problem I am facing is strings. I get string value from my UITextField at at times this value could be empty and as a result an empty string is decoded. How can I return nil if the value is an empty string.
extension Encodable {
var requestDictionary: [String: Any]? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let data = try? encoder.encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
if I have a Struct
let example = Exa(age: 10, name: "")
let dict = example.requestDictionary
print(dict)
I want it to just print ["age": 10] and return nil for the empty string
You can implement your own String encoding method extending KeyedEncodingContainer:
extension KeyedEncodingContainer {
mutating func encode(_ value: String, forKey key: K) throws {
guard !value.isEmpty else { return }
try encodeIfPresent(value, forKey: key)
}
}
Btw your request dictionary can be simplified as:
extension Encodable {
var dictionary: [String: Any]? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return try? JSONSerialization.jsonObject(with: encoder.encode(self)) as? [String: Any]
}
}
Playground testing:
struct Exa: Encodable {
let age: Int
let name: String
}
let example = Exa(age: 10, name: "")
let dict = example.dictionary!
print(dict) // "["age": 10]\n"
I'll just another approach using a property wrapper to mark which properties could be skipped.
#propertyWrapper
struct SkipEmpty {
var wrappedValue: String
}
extension SkipEmpty: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
// nothing to do here, see below
}
}
But to actually skip, you'd also need to create a overload for the KeyedEncodingContainer.encode method for the SkipEmpty type:
extension KeyedEncodingContainer {
mutating func encode(_ value: SkipEmpty, forKey key: K) throws {
if !value.wrappedValue.isEmpty {
try encode(value.wrappedValue, forKey: key) // encode the value here
}
}
}
You could possibly try to make it more generic, e.g. SkipEmpty<T: Codable> and provide another argument for the value to skip or a predicate, etc...
The usage is:
struct Exa: Encodable {
var age: Int
#SkipEmpty var name: String
}

Polymorphically decode a sub-object of a KeyedDecodingContainer in Swift

The following code attempts to create a Codable type AnyBase that would allow you to encode and decode various subclasses of Base. As written, in fails, because it tries to lookup the type objects by a string code, using getTypeFromCode. But if you use the commented-out part instead with hard-coded types, it prints "Sub1", as desired.
Is there a way to adjust this to use something like getTypeFromCode and eliminate the hard-coded types from init?
class Base: Codable {
var foo: Int = 1
var codingTypeKey: String { fatalError("abstract") }
}
class Sub1: Base {
var sub1Prop: Int = 2
override var codingTypeKey: String { "sub1" }
enum CodingKeys: CodingKey {
case sub1Prop
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
sub1Prop = try c.decode(Int.self, forKey: .sub1Prop)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(sub1Prop, forKey: .sub1Prop)
try super.encode(to: encoder)
}
}
class Sub2: Base {
var sub2Prop: Int = 2
override var codingTypeKey: String { "sub2" }
}
func getTypeFromCode(_ typeCode: String) -> Base.Type {
if typeCode == "sub1" { return Sub1.self }
else if typeCode == "sub2" { return Sub2.self }
else { fatalError("bad type code") }
}
class AnyBase: Codable {
let base: Base
init(_ b: Base) { base = b }
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
/*if typeCode == "sub1" {
self.base = try c.decode(Sub1.self, forKey: .base)
} else if typeCode == "sub2" {
self.base = try c.decode(Sub2.self, forKey: .base)
} else {
fatalError("bad type code")
}*/
let typeObj = getTypeFromCode(typeCode)
self.base = try c.decode(typeObj, forKey: .base)
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(base.codingTypeKey, forKey: .type)
try c.encode(base, forKey: .base)
}
enum CodingKeys: CodingKey {
case base, type
}
}
// To to round-trip encode/decode and get the right object back...
let enc = JSONEncoder()
let dec = JSONDecoder()
let sub = Sub1()
let any = AnyBase(sub)
let data = try! enc.encode(any)
let str = String(data:data, encoding:.utf8)!
print(str)
let any2 = try! dec.decode(AnyBase.self, from: data)
print(type(of:any2.base))
Instead of getTypeFromCode (_:) method, you can create an enum BaseType like,
enum BaseType: String {
case sub1, sub2
var type: Base.Type {
switch self {
case .sub1: return Sub1.self
case .sub2: return Sub2.self
}
}
}
Now, in init(from:) get the BaseType using typeCode as rawValue, i.e.
class AnyBase: Codable {
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let typeCode = try c.decode(String.self, forKey: .type)
if let baseType = BaseType(rawValue: typeCode) {
self.base = try c.decode(baseType.type, forKey: .base)
} else {
fatalError("bad type code")
}
}
//rest of the code...
}

Dealing with dynamic value types with Swift and Codeable

So i am trying to decode a JSON via a Codeable model object. One of the properties, say series_id, can either be an integer or a string. If empty, it is null.
How do I handle that using the decodable option in Swift? I currently have:
struct ContentItem: Codable {
let content_id: String?
let series_id: String?
let rank: Int
let score: Float
}
In my main code I do:
do {
let object = try self.decoder.decode(T.self, from: data)
DispatchQueue.main.async {
completionHandler(.success(object))
}
} catch {
...
}
Thanks!
You can use an enum for that. So it could be either a String or an Int or Unknown:
extension ContentItem {
enum SeriesId: Codable {
case text(String)
case number(Int)
case unknown
}
}
Then you can implement as a Decodable:
extension ContentItem.SeriesId {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let text = try? container.decode(String.self) {
self = .text(text)
} else if let number = try? container.decode(Int.self) {
self = .number(number)
} else {
assertionFailure("Unknown SeriesId type")
self = .unknown
}
}
}
Note that you can expand this to manage whatever Empty means (Since it could be a number as well and empty number is unknown).
Also you can expand it to make it encodable too:
extension ContentItem.SeriesId {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let text): try container.encode(text)
case .number(let number): try container.encode(number)
case .unknown: throw NSError(domain: "Unknown case is not encodable", code: -1, userInfo: nil)
}
}
}
So you only need to change the type of the series_id:
struct ContentItem: Codable {
let content_id: String?
let series_id: SeriesId?
let rank: Int
let score: Float
}

How can I use Swift’s Codable to encode into a dictionary?

I have a struct that implements Swift 4’s Codable. Is there a simple built-in way to encode that struct into a dictionary?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
If you don't mind a bit of shifting of data around you could use something like this:
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
}
Or an optional variant
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
Assuming Foo conforms to Codable or really Encodable then you can do this.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
If you want to go the other way(init(any)), take a look at this Init an object conforming to Codable with a dictionary/array
Here are simple implementations of DictionaryEncoder / DictionaryDecoder that wrap JSONEncoder, JSONDecoder and JSONSerialization, that also handle encoding / decoding strategies…
class DictionaryEncoder {
private let encoder = JSONEncoder()
var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy {
set { encoder.dateEncodingStrategy = newValue }
get { return encoder.dateEncodingStrategy }
}
var dataEncodingStrategy: JSONEncoder.DataEncodingStrategy {
set { encoder.dataEncodingStrategy = newValue }
get { return encoder.dataEncodingStrategy }
}
var nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy {
set { encoder.nonConformingFloatEncodingStrategy = newValue }
get { return encoder.nonConformingFloatEncodingStrategy }
}
var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy {
set { encoder.keyEncodingStrategy = newValue }
get { return encoder.keyEncodingStrategy }
}
func encode<T>(_ value: T) throws -> [String: Any] where T : Encodable {
let data = try encoder.encode(value)
return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
}
}
class DictionaryDecoder {
private let decoder = JSONDecoder()
var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy {
set { decoder.dateDecodingStrategy = newValue }
get { return decoder.dateDecodingStrategy }
}
var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy {
set { decoder.dataDecodingStrategy = newValue }
get { return decoder.dataDecodingStrategy }
}
var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy {
set { decoder.nonConformingFloatDecodingStrategy = newValue }
get { return decoder.nonConformingFloatDecodingStrategy }
}
var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy {
set { decoder.keyDecodingStrategy = newValue }
get { return decoder.keyDecodingStrategy }
}
func decode<T>(_ type: T.Type, from dictionary: [String: Any]) throws -> T where T : Decodable {
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try decoder.decode(type, from: data)
}
}
Usage is similar to JSONEncoder / JSONDecoder…
let dictionary = try DictionaryEncoder().encode(object)
and
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
For convenience, I've put this all in a repo… https://github.com/ashleymills/SwiftDictionaryCoding
I have create a library called CodableFirebase and it's initial purpose was to use it with Firebase Database, but it does actually what you need: it creates a dictionary or any other type just like in JSONDecoder but you don't need to do the double conversion here like you do in other answers. So it would look something like:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
There is no built in way to do that.
As answered above if you have no performance issues then you can accept the JSONEncoder + JSONSerialization implementation.
But I would rather go the standard library's way to provide an encoder/decoder object.
class DictionaryEncoder {
private let jsonEncoder = JSONEncoder()
/// Encodes given Encodable value into an array or dictionary
func encode<T>(_ value: T) throws -> Any where T: Encodable {
let jsonData = try jsonEncoder.encode(value)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
class DictionaryDecoder {
private let jsonDecoder = JSONDecoder()
/// Decodes given Decodable type from given array or dictionary
func decode<T>(_ type: T.Type, from json: Any) throws -> T where T: Decodable {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
return try jsonDecoder.decode(type, from: jsonData)
}
}
You can try it with following code:
struct Computer: Codable {
var owner: String?
var cpuCores: Int
var ram: Double
}
let computer = Computer(owner: "5keeve", cpuCores: 8, ram: 4)
let dictionary = try! DictionaryEncoder().encode(computer)
let decodedComputer = try! DictionaryDecoder().decode(Computer.self, from: dictionary)
I am force-trying here to make the example shorter. In production code you should handle the errors appropriately.
I'm not sure if it's the best way but you definitely can do something like:
struct Foo: Codable {
var a: Int
var b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
}
let foo = Foo(a: 1, b: 2)
let dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo))
print(dict)
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
I have modified the PropertyListEncoder from the Swift project into a DictionaryEncoder, simply by removing the final serialisation from dictionary into binary format. You can do the same yourself, or you can take my code from here
It can be used like this:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
In some project, i'm used the swift reflection. But be careful, nested codable objects, are not mapped also there.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
I definitely think that there's some value in just being able to use Codable to encode to/from dictionaries, without the intention of ever hitting JSON/Plists/whatever. There are plenty of APIs which just give you back a dictionary, or expect a dictionary, and it's nice to be able to interchange them easily with Swift structs or objects, without having to write endless boilerplate code.
I've been playing round with some code based on the Foundation JSONEncoder.swift source (which actually does implement dictionary encoding/decoding internally, but doesn't export it).
The code can be found here: https://github.com/elegantchaos/DictionaryCoding
It's still quite rough, but I've expanded it a bit so that, for example, it can fill in missing values with defaults when decoding.
Here is a protocol based solution:
protocol DictionaryEncodable {
func encode() throws -> Any
}
extension DictionaryEncodable where Self: Encodable {
func encode() throws -> Any {
let jsonData = try JSONEncoder().encode(self)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
protocol DictionaryDecodable {
static func decode(_ dictionary: Any) throws -> Self
}
extension DictionaryDecodable where Self: Decodable {
static func decode(_ dictionary: Any) throws -> Self {
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try JSONDecoder().decode(Self.self, from: jsonData)
}
}
typealias DictionaryCodable = DictionaryEncodable & DictionaryDecodable
And here is how to use it:
class AClass: Codable, DictionaryCodable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
struct AStruct: Codable, DictionaryEncodable, DictionaryDecodable {
var name: String
var age: Int
}
let aClass = AClass(name: "Max", age: 24)
if let dict = try? aClass.encode(), let theClass = try? AClass.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theClass.name), age: \(theClass.age)\"")
}
let aStruct = AStruct(name: "George", age: 30)
if let dict = try? aStruct.encode(), let theStruct = try? AStruct.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theStruct.name), age: \(theStruct.age)\"")
}
I wrote a quick gist to handle this (not using the Codable protocol). Be careful, it doesn't type-check any values and doesn't work recursively on values that are encodable.
class DictionaryEncoder {
var result: [String: Any]
init() {
result = [:]
}
func encode(_ encodable: DictionaryEncodable) -> [String: Any] {
encodable.encode(self)
return result
}
func encode<T, K>(_ value: T, key: K) where K: RawRepresentable, K.RawValue == String {
result[key.rawValue] = value
}
}
protocol DictionaryEncodable {
func encode(_ encoder: DictionaryEncoder)
}
There no straight forward way of doing this in Codable. You need to implement Encodable/Decodable protocol for your struct. For your example, you might need to write as below
typealias EventDict = [String:Int]
struct Favorite {
var all:EventDict
init(all: EventDict = [:]) {
self.all = all
}
}
extension Favorite: Encodable {
struct FavoriteKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: FavoriteKey.self)
for eventId in all {
let nameKey = FavoriteKey(stringValue: eventId.key)!
try container.encode(eventId.value, forKey: nameKey)
}
}
}
extension Favorite: Decodable {
public init(from decoder: Decoder) throws {
var events = EventDict()
let container = try decoder.container(keyedBy: FavoriteKey.self)
for key in container.allKeys {
let fav = try container.decode(Int.self, forKey: key)
events[key.stringValue] = fav
}
self.init(all: events)
}
}
I have made a pod here https://github.com/levantAJ/AnyCodable to facilitate decode and encode [String: Any] and [Any]
pod 'DynamicCodable', '1.0'
And you are able to decode & encode [String: Any] and [Any]
import DynamicCodable
struct YourObject: Codable {
var dict: [String: Any]
var array: [Any]
var optionalDict: [String: Any]?
var optionalArray: [Any]?
enum CodingKeys: String, CodingKey {
case dict
case array
case optionalDict
case optionalArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dict = try values.decode([String: Any].self, forKey: .dict)
array = try values.decode([Any].self, forKey: .array)
optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dict, forKey: .dict)
try container.encode(array, forKey: .array)
try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
}
}
After research, we find that if we use the keyword Any in the class which is inherited from the Codable & Decodable it will give the error. So if you want to use a dictionary user with the types of data coming from the server.
For example, the server is sending the dictionary of type [String : Int] then use [String : Int] if you will try [String : Any] it will not work.
Here is dictionary -> object. Swift 5.
extension Dictionary where Key == String, Value: Any {
func object<T: Decodable>() -> T? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
return try? JSONDecoder().decode(T.self, from: data)
} else {
return nil
}
}
}
Come to think of it, the question does not have an answer in the general case, since the Encodable instance may be something not serializable into a dictionary, such as an array:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Other than that, I have written something similar as a framework.