Skip to content

Instantly share code, notes, and snippets.

@gcbrueckmann
Last active January 22, 2023 14:46
Show Gist options
  • Save gcbrueckmann/6a213b748bfe506fa06ee9fcf8a8cb9a to your computer and use it in GitHub Desktop.
Save gcbrueckmann/6a213b748bfe506fa06ee9fcf8a8cb9a to your computer and use it in GitHub Desktop.
Decode arbitrary JSON values using Swift.Decodable
import Foundation
/// Wrapper allowing decoding of arbitrary JSON values,
/// i. e. values whose specific type is not known at compile time.
///
/// This is a workaround for Swift 4’s decoding system not allowing
/// something like `JSONDecoder().decode([String: Any], forKey: .foo)`,
/// because `Any` does not (and cannot) conform to `Decodable`.
///
/// Access the underlying value using the `anyValue` property.
enum JSONValue: Swift.Decodable {
case string(String)
case integer(Int)
case double(Double)
case object([String: JSONValue])
case array([JSONValue])
case bool(Bool)
case null
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringValue = try? container.decode(String.self) {
self = .string(stringValue)
} else if let boolValue = try? container.decode(Bool.self) {
self = .bool(boolValue)
} else if let doubleValue = try? container.decode(Double.self) {
if let intValue = try? container.decode(Int.self),
Double(intValue) == doubleValue
{
self = .integer(intValue)
} else {
self = .double(doubleValue)
}
} else if let arrayValue = try? container.decode([JSONValue].self) {
self = .array(arrayValue)
} else if let objectValue = try? container.decode([String: JSONValue].self) {
self = .object(objectValue)
} else if container.decodeNil() {
self = .null
} else {
throw Swift.DecodingError.typeMismatch(JSONValue.self, Swift.DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep"))
}
}
var anyValue: Any {
switch self {
case let .string(stringValue):
return stringValue
case let .integer(intValue):
return intValue
case let .double(doubleValue):
return doubleValue
case let .object(objectValue):
return objectValue.mapValues { $0.anyValue }
case let .array(arrayValue):
return arrayValue.map { $0.anyValue }
case let .bool(boolValue):
return boolValue
case .null:
return NSNull()
}
}
}
@gcbrueckmann
Copy link
Author

@t3chnogod JSONValue is not meant to be mapped to something else. JSONDecoder and a struct conforming to Decodable would be a way better choice for that. The idea behind JSONValue is to losslessly decode arbitrary JSON payloads much as NSJSONSerialization does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment