7

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?

7
  • Think you might be out of luck with trying to decode Any but maybe you can just try converting bar to a JSON string and then store it that way Commented Sep 18, 2017 at 14:03
  • Codable won't decode a number to a string, though. Commented Sep 18, 2017 at 14:21
  • 1
    Compare How to represent a generic JSON structure in Swift? Commented Sep 18, 2017 at 14:57
  • 1
    @LorenzoB I'm working on quicktype, a tool for generating static types and serializers/deserializers for a number of programming languages, from JSON data. In some cases we can't infer a type from the data (for details on that, see my blog post), so we default to the target language's equivalent of Any. Commented Sep 18, 2017 at 15:17
  • 1
    If you don't at all know the structure of the data in the JSON is at a given point, then you're out of luck at the moment as this isn't yet possible to do efficiently/in a reasonable manner. See bugs.swift.org/browse/SR-5311 Commented Sep 18, 2017 at 23:30

4 Answers 4

16

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
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This is great, thank you. I only had to add <MyCodingKey> to KeyedEncodingContainer, which caused build errors on its own.
Thanks for this, it works great for parsing JSON, I was wondering though, could you tell me how to write a single value to an existing JSONAny?
Does this work in Swift 5? I'm getting XCode complaining about Reference to generic type 'KeyedDecodingContainer' requires arguments in <...>, and trying to "fix" it with invalid fixes
9

Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.

Eg:

import AnyCodable

struct Foo: Codable
{
    var bar: AnyCodable
}

2 Comments

How to get the datatype of the variable bar if i need to use the variable in if else condition
if foo.bar is YourType {} or if let bar = foo.bar as? YourType {} I hope this will work
3

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 :)

3 Comments

This negates all advantages of the Codable protocol and could be extremely expensive for large data sets.
It's a misuse of generics. An almost all cases a JSON string is not generic.
It's also using a product type (a struct) instead of a sum type (an enum). You'll be constantly unwrapping optionals, and overall have a bad time
1

You could try BeyovaJSON

import BeyovaJSON

struct Foo: Codable {
  let bar: JToken
}

let foo = try! JSONDecoder().decode(Foo.self, from: data)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.