How to Use Any in Codable Type

How to use Any in Codable Type

Codable needs to know the type to cast to.

Firstly I would try to address the issue of not knowing the type, see if you can fix that and make it simpler.

Otherwise the only way I can think of solving your issue currently is to use generics like below.

struct Person {
var id: T
var name: String
}

let person1 = Person(id: 1, name: "John")
let person2 = Person(id: "two", name: "Steve")

Swift Codable how to use Any type?

You can get string values for your label by conforming to rawRepresentable protocol:

enum MyValue: Codable, RawRepresentable {


var rawValue: String {
switch self {
case .string(let stringVal):
return stringVal
case .innerItem(let myVal):
return String(describing: myVal)
}
}

typealias RawValue = String

init?(rawValue: String) {
return nil
}



case string(String)
case innerItem(InnerItem)

}

let myVal = MyValue.string("testString")
var strVal: String = myVal.rawValue // testString

Any when decoding JSON with Codable?

I ended up having to implement my own class to encode/decode Any values. It's not pretty, but it seems to work:

class JSONAny: Codable {
public let value: Any

static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}

static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}

static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}

static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}

static func decode(from container: inout KeyedDecodingContainer, forKey key: MyCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}

static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}

static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}

static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: MyCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}

static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = MyCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}

static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}

public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: MyCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}

public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: MyCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}

class JSONNull: Codable {
public init() {
}

public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}

class MyCodingKey : CodingKey {
let key: String

required init?(intValue: Int) {
return nil
}

required init?(stringValue: String) {
key = stringValue
}

var intValue: Int? {
return nil
}

var stringValue: String {
return key
}
}

JSON Codable - What to do with [Any]?

You need to use an unkeyed container for this when decoding in your custom init(from:).

If you know there is always one string followed by one integer you can define the struct for the values like this

struct Value: Decodable {
let string: String?
let integer: Int?

init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
string = try container.decodeIfPresent(String.self)
integer = try container.decodeIfPresent(Int.self)
}
}

If there could be more elements you can use a loop instead

struct Value: Decodable {
let strings: [String]
let integers: [Int]

init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var strings = [String]()
var integers = [Int]()

while !container.isAtEnd {
do {
let string = try container.decode(String.self)
strings.append(string)
} catch {
let integer = try container.decode(Int.self)
integers.append(integer)
}
}
self.integers = integers
self.strings = strings
}
}

How Handle Any Type of Data in Codable Swift

You can declare the Type enum with associated values as defined below:

   struct OverviewWorkout : Decodable {

var type: WorkoutType

enum WorkoutType: String, Codable {
case workout(data: Workout)
case coach(data: CoachInstruction)
case bodyArea(data: [Workout])
case title(data: Title)
case group(data: [Workout])
case trainer(data: Trainer)
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(WorkoutType.self, forKey: .type)
switch type {
case .workout:
let data = try container.decode(Workout.self, forKey: .data)
self = .workout(data: data)
case .trainer:
let data = try container.decode(Trainer.self, forKey: .data)
self = .trainer(data: data)
.
.
.

}

}
}

I was short of time so couldn't compile it but I hope you this will give you an idea. Additionally, Sharing a reference article for you. [:D You might have visited already]

How can I write a function which accepts any object which conforms to Codable

Your signature is incorrect. You don't want a Codable. You want a generic type that conforms to Codable. Specifically, you only really need one that conforms to Encodable:

func sendToServer(message: Message) { ... }

A "Codable" or "Encodable" (the protocols) can't itself be encoded. It doesn't have any information about what to encode. But types that conform to Encodable provide that information.

Codable [String: Any] dictionary

Is it possible to use Codable with [String: Any] dictionary?

no, you can't with Codable but you can make this dictionary in a new model.

Replace

public var message: [String: Any]?

with

public var message: MessageModel?

struct MessageModel: Codable { }

multiple types in Codable

I definitely agree with @vadian. What you have is an optional rating. IMO this is a perfect scenario for using a propertyWrapper. This would allow you to use this Rated type with any model without having to manually implement a custom encoder/decoder to each model:

@propertyWrapper
struct RatedDouble: Codable {

var wrappedValue: Double?
init(wrappedValue: Double?) {
self.wrappedValue = wrappedValue
}

private struct Rated: Decodable {
let value: Double
}

public init(from decoder: Decoder) throws {
do {
wrappedValue = try decoder.singleValueContainer().decode(Rated.self).value
} catch DecodingError.typeMismatch {
let bool = try decoder.singleValueContainer().decode(Bool.self)
guard !bool else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Corrupted data"))
}
wrappedValue = nil
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
guard let double = wrappedValue else {
try container.encode(false)
return
}
try container.encode(["value": double])
}
}

Usage:

struct AccountState: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
@RatedDouble var rated: Double?
}


let json1 = #"{"id":550,"favorite":false,"rated":{"value":9.0},"watchlist":false}"#
let json2 = #"{"id":550,"favorite":false,"rated":false,"watchlist":false}"#
do {
let accountState1 = try JSONDecoder().decode(AccountState.self, from: Data(json1.utf8))
print(accountState1.rated ?? "nil") // "9.0\n"
let accountState2 = try JSONDecoder().decode(AccountState.self, from: Data(json2.utf8))
print(accountState2.rated ?? "nil") // "nil\n"
let encoded1 = try JSONEncoder().encode(accountState1)
print(String(data: encoded1, encoding: .utf8) ?? "nil")
let encoded2 = try JSONEncoder().encode(accountState2)
print(String(data: encoded2, encoding: .utf8) ?? "nil")
} catch {
print(error)
}

This would print:

9.0

nil

{"watchlist":false,"id":550,"favorite":false,"rated":{"value":9}}

{"watchlist":false,"id":550,"favorite":false,"rated":false}



Related Topics



Leave a reply



Submit