6
votes

I want to encode Dictionary to json with JSONEncoder. It seems like a Request, receive a dictionary as parameter and encode it to json as http body. The code is looks like this:

let dict = ["name": "abcde"]

protocol Request {
    var params: [String: Encodable] { get set }
    func encode<T>(_ value: T) throws -> Data where T : Encodable
}

extension Request {
    func encode<T>(_ value: T) throws -> Data where T : Encodable {
        return try JSONEncoder().encode(value)
    }

    var body: Data? {
        if let encoded = try? self.encode(self.params) {
            return encoded
        }
        return nil
    }
}

struct BaseRequest: Request {
    var params: [String : Encodable]
}

let req = BaseRequest(params: dict)
let body = req.body

But this code occurs error

Fatal error: Dictionary<String, Encodable> does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.

How could I make this encodable?

2
Why not use JSONSerializer instead? Do you want to prevent Any dependencies?Timofey Solonin
Yes I need the params as [String: Any]鸡肉味嘎嘣脆
What are the types that can end up as values in the dictionary? It’s like not truly Any but one of several know types, right? Often the best solution to this is to make an enum with those types as associated values which confirms to Encodable.Itai Ferber
Finally I add associatetype in Request鸡肉味嘎嘣脆

2 Answers

3
votes

You have to introduce type erasure as follows:

struct AnyEncodable: Encodable {

    let value: Encodable
    init(value: Encodable) {
        self.value = value
    }

    func encode(to encoder: Encoder) throws {
        try value.encode(to: encoder)
    }

}

struct Model: Encodable {

    var params: [String: AnyEncodable]

}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(
    Model(
        params: [
            "hello" : AnyEncodable.init(value: "world")
        ]
    ).params
)
print(String(data: json, encoding: .utf8))
-1
votes

If you want to define your struct as conforming to Codable, you can do it like this:

struct Model: Codable {
    var param1: String
    var param2: Int
}

let model = Model(param1: "test", param2: 0)
let encoded = try? JSONEncoder().encode(model)
let decoded = try? JSONDecoder().decode(Model.self, from: encoded!)

It won't really work if you set params: [String: Any] because the encoders/decoders don't know how to encode/decode Any, but they can do it for the primitive types.

If you want more help, you should read more about the new Codable protocol. I recommend this: https://hackernoon.com/everything-about-codable-in-swift-4-97d0e18a2999