Skip to content

Instantly share code, notes, and snippets.

@Jimmy-Prime
Last active May 25, 2020 07:48
Show Gist options
  • Save Jimmy-Prime/5a003e0e04366a6c8a14732e0d765803 to your computer and use it in GitHub Desktop.
Save Jimmy-Prime/5a003e0e04366a6c8a14732e0d765803 to your computer and use it in GitHub Desktop.
JSON Codable
import Foundation
enum JSON {
case null
case bool(Bool)
case int64(Int64)
case double(Double)
case string(String)
indirect case array([JSON])
indirect case dictionary([String: JSON])
}
extension JSON {
struct CodingKeys: CodingKey {
var stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
}
extension JSON: Decodable {
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
self = JSON(from: container)
} else if let container = try? decoder.unkeyedContainer() {
self = JSON(from: container)
} else if let container = try? decoder.singleValueContainer() {
self = try JSON(from: container)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: ""))
}
}
private init(from container: SingleValueDecodingContainer) throws {
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Int64.self) {
self = .int64(value)
} else if let value = try? container.decode(Double.self) {
self = .double(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: ""))
}
}
private init(from container: UnkeyedDecodingContainer) {
var container = container
var array: [JSON] = []
while !container.isAtEnd {
if let isNil = try? container.decodeNil(), isNil {
array.append(.null)
} else if let value = try? container.decode(Bool.self) {
array.append(.bool(value))
} else if let value = try? container.decode(Int64.self) {
array.append(.int64(value))
} else if let value = try? container.decode(Double.self) {
array.append(.double(value))
} else if let value = try? container.decode(String.self) {
array.append(.string(value))
} else if let value = try? container.nestedContainer(keyedBy: CodingKeys.self) {
array.append(JSON(from: value))
} else if let value = try? container.nestedUnkeyedContainer() {
array.append(JSON(from: value))
}
}
self = .array(array)
}
private init(from container: KeyedDecodingContainer<CodingKeys>) {
var dict: [String: JSON] = [:]
for key in container.allKeys {
if let isNil = try? container.decodeNil(forKey: key), isNil {
dict[key.stringValue] = .null
} else if let value = try? container.decode(Bool.self, forKey: key) {
dict[key.stringValue] = .bool(value)
} else if let value = try? container.decode(Int64.self, forKey: key) {
dict[key.stringValue] = .int64(value)
} else if let value = try? container.decode(Double.self, forKey: key) {
dict[key.stringValue] = .double(value)
} else if let value = try? container.decode(String.self, forKey: key) {
dict[key.stringValue] = .string(value)
} else if let value = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: key) {
dict[key.stringValue] = JSON(from: value)
} else if let value = try? container.nestedUnkeyedContainer(forKey: key) {
dict[key.stringValue] = JSON(from: value)
}
}
self = .dictionary(dict)
}
}
extension JSON: Encodable {
func encode(to encoder: Encoder) throws {
switch self {
case .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
case .bool(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .int64(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .double(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .string(let value):
var container = encoder.singleValueContainer()
try container.encode(value)
case .array(let array):
var container = encoder.unkeyedContainer()
for value in array {
try container.encode(value)
}
case .dictionary(let dict):
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dict {
try container.encode(value, forKey: CodingKeys(stringValue: key))
}
}
}
}
extension JSON: CustomDebugStringConvertible {
var debugDescription: String {
switch self {
case .null:
return "null"
case .bool(let value):
return String(value)
case .int64(let value):
return String(value)
case .double(let value):
return String(value)
case .string(let value):
return String(value)
case .array(let array):
let value = array.map { $0.debugDescription }.joined(separator: ", ")
return "[\(value)]"
case .dictionary(let dict):
let value = dict.keys.map { key in "\(key): \(dict[key]!.debugDescription)" }.joined(separator: ", ")
return "{\(value)}"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment