Flight-School / AnyCodable

Type-erased wrappers for Encodable, Decodable, and Codable values
https://flight.school/books/codable
MIT License
1.28k stars 132 forks source link

Cannot convert value of type '<Type>' to expected dictionary value type 'AnyEncodable' #52

Closed RichMarketApp closed 3 years ago

RichMarketApp commented 3 years ago

Hi, I have a problem with assigning data to the dictionary that should be encodable. The Data struct inherit from Codable protocol and contains four different variable types.

I am trying to convert this struct to dictionary that will represent some other model. Please find a dummy code below:

struct Data: Codable {
    let string: String
    let int: Int
    let double: Double
    let bool: Bool
}

let data: Data
var body: [String : AnyEncodable]? {
        [
            "string": data.string,
            "int": data.int,
            "double": data.double,
            "bool": data.bool
        ]
    }

When the project is compiling I am getting four errors:

Cannot convert value of type 'String' to expected dictionary value type 'AnyEncodable' Cannot convert value of type 'Int' to expected dictionary value type 'AnyEncodable' Cannot convert value of type 'Double' to expected dictionary value type 'AnyEncodable' Cannot convert value of type 'Bool' to expected dictionary value type 'AnyEncodable'

Is there any simple way to fix it and left the code clean?

minacle commented 3 years ago

Quick solution

Just wrap the values with .init like this

var body: [String: AnyEncodable]? =
    ["string": .init(data.string),
     "int": .init(data.int),
     "double": .init(data.double),
     "bool": .init(data.bool)]

Explanation

Custom types like AnyEncodable requires explicit type casting, because the Swift compiler does not know that how to cast random type to the custom type, unless the (random) type inherits custom type.

It is easy to understand what is going on when we look more closer. 🔎

let string: String = "Hello"
let int: Int = 42

let anyEncodableString = string as AnyEncodable  // Error: Cannot convert value of type 'String' to type 'AnyEncodable' in coercion
let anyEncodableInt = int as AnyEncodable  // Error: Cannot convert value of type 'Int' to type 'AnyEncodable' in coercion
let anyEncodableFloat = 3.14159 as AnyEncodable  // No problem because `3.14159` is a Float literal
let anyEncodableBool = true as AnyEncodable  // No problem because `true` is a Boolean literal

We know that string and int are from their literal types, but compiler does not think so. string is String typed value, not a String literal. int is Int typed value, not an Integer literal.


Q. Wait, but AnyHashable does not not make any compile error. How to explain about this?

Because AnyHashable is a member of Swift Standard Library. It is explicitly defined in Swift itself to implicitly wrap the value with AnyHashable. But AnyEncodable is not. We cannot make any implicit things without modifying Swift itself.

See also