Last active
January 22, 2023 14:46
-
-
Save gcbrueckmann/6a213b748bfe506fa06ee9fcf8a8cb9a to your computer and use it in GitHub Desktop.
Decode arbitrary JSON values using Swift.Decodable
This file contains 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
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() | |
} | |
} | |
} |
@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
How do I map JSONValue to other structs