With Swift 3 JSONSerialization
, if a part of your data model was completely dynamic, you could always just leave the deserialized data at Any
and let the consumer deal with it.
With Swift 4's Codable
, I'd like to do this:
struct Foo : Codable {
let bar: Any;
}
But I get
JSONPlayground.playground:4:9: note: cannot automatically synthesize 'Decodable' because 'Any' does not conform to 'Decodable'
let bar: Any;
^
That alone wouldn't be the end of the world if I could implement my own Decodable
, but that would require the Decoder
to support decoding to Any
, which as far as I can tell it does not. For example:
extension Foo {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let result = try container.decode(AnyClass.self)
}
}
gives
error: JSONPlayground.playground:4:36: error: cannot invoke 'decode' with an argument list of type '(AnyClass.Protocol)'
let result = try container.decode(AnyClass.self)
^
Is there any solution to this?
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
}
}
You could try BeyovaJSON
import BeyovaJSON
struct Foo: Codable {
let bar: JToken
}
let foo = try! JSONDecoder().decode(Foo.self, from: data)
Simply you can use AnyCodable
type from Matt Thompson's cool library AnyCodable.
Eg:
import AnyCodable
struct Foo: Codable
{
var bar: AnyCodable
}
I solved my problem creating an AnyValue struct to allow Encode and Decode Any values from JSON:
To use it is pretty simple:
class MyClass: Codable {
var data: [String: AnyValue?]?
init(data: [String: AnyValue?]) {
self.data = data
}
}
let data = ["a": AnyValue(3), "b": AnyValue(true), "c": AnyValue("Rodrigo"), "d": AnyValue(3.3)]
let myClass = MyClass(data: data)
if let json = JsonUtil<MyClass>.toJson(myClass) {
print(json) // {"data":{"d":3.3,"b":true,"c":"Rodrigo","a":3}}
if let data = JsonUtil<MyClass>.from(json: json)?.data {
print(data["a"]??.value() ?? "nil") // 3
print(data["b"]??.value() ?? "nil") // true
print(data["c"]??.value() ?? "nil") // Rodrigo
print(data["d"]??.value() ?? "nil") // 3.3
}
}
AnyValue struct:
struct AnyValue: Codable {
private var int: Int?
private var string: String?
private var bool: Bool?
private var double: Double?
init(_ int: Int) {
self.int = int
}
init(_ string: String) {
self.string = string
}
init(_ bool: Bool) {
self.bool = bool
}
init(_ double: Double) {
self.double = double
}
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self.int = int
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self.string = string
return
}
if let bool = try? decoder.singleValueContainer().decode(Bool.self) {
self.bool = bool
return
}
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self.double = double
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let anyValue = self.value() {
if let value = anyValue as? Int {
try container.encode(value)
return
}
if let value = anyValue as? String {
try container.encode(value)
return
}
if let value = anyValue as? Bool {
try container.encode(value)
return
}
if let value = anyValue as? Double {
try container.encode(value)
return
}
}
try container.encodeNil()
}
func value() -> Any? {
return self.int ?? self.string ?? self.bool ?? self.double
}
}
And I created a JsonUtil struct too:
struct JsonUtil<T: Codable> {
public static func from(json: String) -> T? {
if let jsonData = json.data(using: .utf8) {
let jsonDecoder = JSONDecoder()
do {
return try jsonDecoder.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return nil
}
public static func toJson(_ obj: T) -> String? {
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(obj)
return String(data: jsonData, encoding: String.Encoding.utf8)
} catch {
print(error)
return nil
}
}
}
If you need a new type like [String] per example, you just need add it on AnyValue struct.
Good luck :)