Files

103 lines
3.8 KiB
Swift

import Foundation
public enum JSONValue: Codable, Hashable, Sendable {
case object([String: JSONValue])
case array([JSONValue])
case string(String)
case number(Double)
case bool(Bool)
case null
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([JSONValue].self) {
self = .array(value)
} else {
self = .object(try container.decode([String: JSONValue].self))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .object(let value): try container.encode(value)
case .array(let value): try container.encode(value)
case .string(let value): try container.encode(value)
case .number(let value): try container.encode(value)
case .bool(let value): try container.encode(value)
case .null: try container.encodeNil()
}
}
public static func decode(data: Data) throws -> JSONValue {
try JSONDecoder().decode(JSONValue.self, from: data)
}
public var stringValue: String {
switch self {
case .string(let value): return value
case .number(let value):
return value.rounded() == value ? String(format: "%.0f", value) : String(value)
case .bool(let value): return String(value)
case .null: return ""
case .object, .array:
guard let data = try? JSONEncoder().encode(self) else { return "" }
return String(decoding: data, as: UTF8.self)
}
}
public subscript(key: String) -> JSONValue? {
guard case .object(let value) = self else { return nil }
return value[key]
}
public subscript(index: Int) -> JSONValue? {
guard case .array(let value) = self else { return nil }
let resolved = index >= 0 ? index : value.count + index
guard value.indices.contains(resolved) else { return nil }
return value[resolved]
}
}
public extension JSONValue {
var foundationObject: Any {
switch self {
case .object(let value): value.mapValues(\.foundationObject)
case .array(let value): value.map(\.foundationObject)
case .string(let value): value
case .number(let value): value
case .bool(let value): value
case .null: NSNull()
}
}
static func bridge(_ value: Any?) -> JSONValue {
guard let value else { return .null }
if value is NSNull { return .null }
if let value = value as? JSONValue { return value }
if let value = value as? String { return .string(value) }
if let value = value as? Bool { return .bool(value) }
if let value = value as? NSNumber {
if CFGetTypeID(value) == CFBooleanGetTypeID() { return .bool(value.boolValue) }
return .number(value.doubleValue)
}
if let value = value as? [Any] { return .array(value.map(bridge)) }
if let value = value as? [String: Any] { return .object(value.mapValues(bridge)) }
if let value = value as? NSDictionary {
var object: [String: JSONValue] = [:]
for (key, item) in value { object[String(describing: key)] = bridge(item) }
return .object(object)
}
if let value = value as? NSArray { return .array(value.map(bridge)) }
return .string(String(describing: value))
}
}