Last active
May 25, 2020 07:48
-
-
Save Jimmy-Prime/5a003e0e04366a6c8a14732e0d765803 to your computer and use it in GitHub Desktop.
JSON Codable
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 | |
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