Skip to content

Instantly share code, notes, and snippets.

@valeriomazzeo
Created May 18, 2020 10:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save valeriomazzeo/28c996ae9e6cd95456a3687b40edc5d6 to your computer and use it in GitHub Desktop.
Save valeriomazzeo/28c996ae9e6cd95456a3687b40edc5d6 to your computer and use it in GitHub Desktop.
A JSON data structure conforming to Codable
//
// JSON.swift
//
// Created by Valerio Mazzeo on 26/03/2018.
//
import Foundation
public enum JSON {
case object(JSON.Object)
case array(JSON.Array)
case string(String)
case number(Double)
case bool(Bool)
case null
case unencoded(Encodable)
}
public extension JSON {
typealias Array = [JSON]
typealias Object = [String: JSON]
}
public extension JSON {
init(from value: Encodable) {
self = .unencoded(value)
}
init(_ value: JSON.Object) {
self = .object(value)
}
init(_ value: JSON.Array) {
self = .array(value)
}
init(_ value: String) {
self = .string(value)
}
init(_ value: Int) {
self = .number(Double(value))
}
init(_ value: Float) {
self = .number(Double(value))
}
init(_ value: Double) {
self = .number(value)
}
init(_ value: Bool) {
self = .bool(value)
}
init(jsonObject: Any) throws {
switch jsonObject {
case let value as String:
self = .string(value)
case let value as Bool where type(of: jsonObject) is Bool.Type:
self = .bool(value)
case let value as Int:
self = .number(Double(value))
case let value as Int8:
self = .number(Double(value))
case let value as Int16:
self = .number(Double(value))
case let value as Int32:
self = .number(Double(value))
case let value as Int64:
self = .number(Double(value))
case let value as UInt:
self = .number(Double(value))
case let value as UInt8:
self = .number(Double(value))
case let value as UInt16:
self = .number(Double(value))
case let value as UInt32:
self = .number(Double(value))
case let value as UInt64:
self = .number(Double(value))
case let value as [Any?]:
self = .array(try value.map { try JSON(jsonObject: $0 ?? NSNull()) })
case let value as [String: Any?]:
self = .object(try value.mapValues { try JSON(jsonObject: $0 ?? NSNull()) })
case let value as Float:
self = .number(Double(value))
case let value as Double:
self = .number(value)
case let value as Decimal:
self = .number(NSDecimalNumber(decimal: value).doubleValue)
case let value as NSDecimalNumber:
self = .number(value.doubleValue)
case is NSNull:
self = .null
case let value as NSNumber:
#if os(Linux)
self = .number(value.doubleValue)
#else
if value === kCFBooleanTrue as NSNumber {
self = .bool(true)
} else if value === kCFBooleanFalse as NSNumber {
self = .bool(false)
} else {
self = .number(value.doubleValue)
}
#endif
default:
throw DecodingError.typeMismatch(
type(of: jsonObject),
DecodingError.Context(codingPath: [], debugDescription: "Invalid value cannot be decoded")
)
}
}
}
extension JSON: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, JSON)...) {
var dictionary: JSON.Object = [:]
for (key, value) in elements {
dictionary[key] = value
}
self.init(dictionary)
}
}
extension JSON: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: JSON...) {
self.init(elements)
}
}
extension JSON: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self.init(value)
}
}
extension JSON: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self.init(value)
}
}
extension JSON: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self.init(value)
}
}
extension JSON: ExpressibleByFloatLiteral {
public init(floatLiteral value: Float) {
self.init(value)
}
}
public extension JSON {
static let defaultEncoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
return encoder
}()
static let defaultDecoder: JSONDecoder = {
let encoder = JSONDecoder()
encoder.dateDecodingStrategy = .iso8601
return encoder
}()
func encode(encoder: JSONEncoder = JSON.defaultEncoder) throws -> Foundation.Data {
return try encoder.encode(self)
}
func decode<T: Decodable>(_ type: T.Type, decoder: JSONDecoder = JSON.defaultDecoder) throws -> T {
return try decoder.decode(type, from: self.encode())
}
}
public extension JSON {
subscript(key: String) -> JSON? {
return self.object?[key]
}
subscript(index: Int) -> JSON? {
return self.array?[index]
}
}
public extension JSON {
var object: JSON.Object? {
switch self {
case .object(let value):
return value
default:
return nil
}
}
var array: JSON.Array? {
switch self {
case .array(let value):
return value
default:
return nil
}
}
var string: String? {
switch self {
case .string(let value):
return value
default:
return nil
}
}
var int: Int? {
switch self {
case .number(let value) where value == value.rounded():
return Int(value)
default:
return nil
}
}
var double: Double? {
switch self {
case .number(let value):
return value
default:
return nil
}
}
var bool: Bool? {
switch self {
case .bool(let value):
return value
default:
return nil
}
}
var isUnencoded: Bool {
switch self {
case .unencoded:
return true
default:
return false
}
}
var isNull: Bool {
switch self {
case .null:
return true
default:
return false
}
}
}
extension JSON: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if container.decodeNil() {
self = .null
} else if let value = try? container.decode(JSON.Object.self) {
self = .object(value)
} else if let value = try? container.decode(JSON.Array.self) {
self = .array(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Invalid value cannot be decoded"
)
}
}
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()
case .unencoded(let value):
try value.encode(to: encoder)
}
}
}
extension JSON: Equatable {
public static func == (lhs: JSON, rhs: JSON) -> Bool {
switch (lhs, rhs) {
case (.object(let lhs), .object(let rhs)):
return lhs == rhs
case (.array(let lhs), .array(let rhs)):
return lhs == rhs
case (.string(let lhs), .string(let rhs)):
return lhs == rhs
case (.number(let lhs), .number(let rhs)) where lhs == lhs.rounded() && rhs == rhs.rounded():
return lhs == rhs
case (.number(let lhs), .number(let rhs)):
return lhs.distance(to: rhs) <= 0.001
case (.bool(let lhs), .bool(let rhs)):
return lhs == rhs
case (.null, .null):
return true
case (.unencoded, .unencoded):
return false
default:
return false
}
}
}
public extension Dictionary where Key == String, Value == JSON {
init(from value: Encodable) throws {
guard let object = try JSON.unencoded(value).decode(JSON.self).object else {
throw DecodingError.typeMismatch(
JSON.Object.self,
DecodingError.Context(codingPath: [], debugDescription: "Value is not representable as a JSON.Object")
)
}
self = object
}
}
public extension Array where Element == JSON {
init(from value: Encodable) throws {
guard let array = try JSON.unencoded(value).decode(JSON.self).array else {
throw DecodingError.typeMismatch(
JSON.Object.self,
DecodingError.Context(codingPath: [], debugDescription: "Value is not representable as a JSON.Array")
)
}
self = array
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment