Skip to content

Instantly share code, notes, and snippets.

@dineybomfim
Last active December 12, 2022 12:32
Show Gist options
  • Save dineybomfim/5233406ada3d800d9d7387fd8896f8c8 to your computer and use it in GitHub Desktop.
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`
// 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