Last active
December 12, 2022 12:32
-
-
Save dineybomfim/5233406ada3d800d9d7387fd8896f8c8 to your computer and use it in GitHub Desktop.
Wondering how to parse dynamic JSON with Swift Codable without dealing with `init(from:Decoder)` and `encode(to: Encoder)`? The answer is `AnyCodable`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Full Swift Codable object compatible with dynamic JSON structures. Able to encode and decode any kind of JSON. | |
// | |
// How to use it? | |
// | |
// Try it out on your playground at the end of the file: | |
public struct AnyCodable { | |
// MARK: - Properties | |
public let value: Any? | |
public var keyedValues: [String : Any]? { value as? [String : Any] } | |
// MARK: - Constructors | |
public init(_ initialValue: Any?) { value = initialValue } | |
// MARK: - Protected Methods | |
// MARK: - Exposed Methods | |
public func map<T : Codable>(to type: T.Type) -> T? { | |
guard let item = value else { return nil } | |
do { | |
let data = try JSONSerialization.data(withJSONObject: item) | |
return try JSONDecoder().decode(type, from: data) | |
} catch { | |
print("\(error)") | |
return nil | |
} | |
} | |
} | |
// MARK: - Extension - AnyCodable Codable | |
extension AnyCodable : Codable { | |
public init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
if let content = try? container.decode(String.self) { | |
value = content | |
} else if let content = try? container.decode(Bool.self) { | |
value = content | |
} else if let content = try? container.decode([String: AnyCodable].self) { | |
value = content.mapValues { $0.value } | |
} else if let content = try? container.decode([AnyCodable].self) { | |
value = content.map { $0.value } | |
} else if let content = try? container.decode(Int.self) { | |
value = content | |
} else if let content = try? container.decode(Double.self) { | |
value = content | |
} else if container.decodeNil() { | |
value = nil | |
} else { | |
throw DecodingError.dataCorruptedError(in: container, debugDescription: "") | |
} | |
} | |
public func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
guard let item = value else { | |
try container.encodeNil() | |
return | |
} | |
switch item { | |
case let content as String: | |
try container.encode(content) | |
case let content as Bool: | |
try container.encode(content) | |
case let content as Int: | |
try container.encode(content) | |
case let content as Float: | |
try container.encode(content) | |
case let content as Double: | |
try container.encode(content) | |
case let content as [Any?]: | |
try container.encode(content.map { AnyCodable($0) }) | |
case let content as [String : Any?]: | |
try container.encode(content.mapValues { AnyCodable($0) }) | |
case is NSNull: | |
try container.encodeNil() | |
default: | |
throw EncodingError.invalidValue(item, .init(codingPath: container.codingPath, debugDescription: "")) | |
} | |
} | |
} | |
// Usage: | |
// | |
// let json = "{\"property1\": \"value 1\", \"property2\": true, \"myDyamicNode\": { \"subNode\": \"String\", \"anotherNode\" : 1 }}" | |
// | |
// public struct MyModel :Codable { | |
// let property1: String | |
// let property2: Bool | |
// let myDyamicNode: AnyCodable | |
// let myOptionalDyamicNode: AnyCodable? | |
// } | |
// | |
// let myModel = try! JSONDecoder().decode(MyModel.self, from: json.data(using: .utf8)!) | |
// | |
// print(myModel.property1) | |
// print(myModel.myDyamicNode) | |
// | |
// public struct SubStrongTyped :Codable { | |
// let subNode: String | |
// let anotherNode: Int | |
// } | |
// | |
// let subNode = myModel.myDyamicNode.map(to: SubStrongTyped.self) | |
// print(subNode?.subNode) | |
// print(subNode?.anotherNode) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment