83 lines
2.5 KiB
Swift
83 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
/// A JSON value representation. This is a bit more useful than the naïve `[String:Any]` type
|
|
/// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable`
|
|
/// and `Codable`, so that you can compare values for equality and code and decode them into data
|
|
/// or strings.
|
|
@dynamicMemberLookup public enum JSON: Equatable {
|
|
case string(String)
|
|
case number(Double)
|
|
case object([String:JSON])
|
|
case array([JSON])
|
|
case bool(Bool)
|
|
case null
|
|
}
|
|
|
|
extension JSON: Codable {
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
|
|
var container = encoder.singleValueContainer()
|
|
|
|
switch self {
|
|
case let .array(array):
|
|
try container.encode(array)
|
|
case let .object(object):
|
|
try container.encode(object)
|
|
case let .string(string):
|
|
try container.encode(string)
|
|
case let .number(number):
|
|
try container.encode(number)
|
|
case let .bool(bool):
|
|
try container.encode(bool)
|
|
case .null:
|
|
try container.encodeNil()
|
|
}
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
|
|
let container = try decoder.singleValueContainer()
|
|
|
|
if let object = try? container.decode([String: JSON].self) {
|
|
self = .object(object)
|
|
} else if let array = try? container.decode([JSON].self) {
|
|
self = .array(array)
|
|
} else if let string = try? container.decode(String.self) {
|
|
self = .string(string)
|
|
} else if let bool = try? container.decode(Bool.self) {
|
|
self = .bool(bool)
|
|
} else if let number = try? container.decode(Double.self) {
|
|
self = .number(number)
|
|
} else if container.decodeNil() {
|
|
self = .null
|
|
} else {
|
|
throw DecodingError.dataCorrupted(
|
|
.init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension JSON: CustomDebugStringConvertible {
|
|
|
|
public var debugDescription: String {
|
|
switch self {
|
|
case .string(let str):
|
|
return str.debugDescription
|
|
case .number(let num):
|
|
return num.debugDescription
|
|
case .bool(let bool):
|
|
return bool.description
|
|
case .null:
|
|
return "null"
|
|
default:
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting = [.prettyPrinted]
|
|
return try! String(data: encoder.encode(self), encoding: .utf8)!
|
|
}
|
|
}
|
|
}
|
|
|
|
extension JSON: Hashable {}
|