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
How to Dismiss Viewcontroller in Swift
Setting Direction for Uiswipegesturerecognizer
How to Rotate Text for Uibutton and Uilabel in Objective-C
Nspersistentcontainer Concurrency for Saving to Core Data
How to Programmatically Determine If My App Is Running in the iPhone Simulator
Renew Push Certificate and Keep Current App Store App Working
How to Create Managedobjectcontext Using Swift 3 in Xcode 8
How to Reset the Privacy Settings in iOS
Replacement for C-Style Loop in Swift 2.2
How Can a Web Application Send Push Notifications to iOS Devices
How to Load a Xib File in a Uiview
Get Image from Documents Directory Swift
Uitextview - Setting Font Not Working with iOS 6 on Xcode 5
Save Data to .Plist File in Swift
Does an iOS App Have Write Access Inside Its Bundle