mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
322 lines
9.9 KiB
Swift
322 lines
9.9 KiB
Swift
//
|
|
// Json.swift
|
|
// JsonRpc2
|
|
//
|
|
// Created by Dmitry Bespalov on 15.12.21.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// wrappers over basic json types in order to be able to encode and decode
|
|
// dynamic arrays or dictionaries in other types
|
|
// otherwise we have to know in advance concrete type fro decoding, which is possible
|
|
// but doesn't solve a situation when decoding of that concrete type fails
|
|
// and we still want to see what actual json we received.
|
|
|
|
public enum Json {
|
|
public struct Object: Hashable {
|
|
public var members: [String: Element]
|
|
|
|
public init(members: [String: Element]) {
|
|
self.members = members
|
|
}
|
|
}
|
|
|
|
public struct Array: Hashable {
|
|
public var elements: [Element]
|
|
|
|
public init(elements: [Element]) {
|
|
self.elements = elements
|
|
}
|
|
}
|
|
|
|
public enum Element: Hashable {
|
|
case object(Object)
|
|
case array(Array)
|
|
case string(String)
|
|
case int(Int)
|
|
case uint(UInt)
|
|
case double(Double)
|
|
case bool(Bool)
|
|
case null
|
|
}
|
|
|
|
struct Key: CodingKey {
|
|
var stringValue: String
|
|
|
|
init?(stringValue: String) {
|
|
self.stringValue = stringValue
|
|
}
|
|
|
|
var intValue: Int?
|
|
|
|
init?(intValue: Int) {
|
|
self.intValue = intValue
|
|
self.stringValue = String(intValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Json.Element: Encodable {
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
|
|
// for each case with associated value, encode the value directly
|
|
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 .int(let value):
|
|
try container.encode(value)
|
|
case .uint(let value):
|
|
try container.encode(value)
|
|
case .double(let value):
|
|
try container.encode(value)
|
|
case .bool(let value):
|
|
try container.encode(value)
|
|
case .null:
|
|
// for null, encodeNil
|
|
try container.encodeNil()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Json.Element: Decodable {
|
|
public init(from decoder: Decoder) throws {
|
|
// try decode primitve value
|
|
if let container = try? decoder.singleValueContainer() {
|
|
if let value = try? container.decode(String.self) {
|
|
self = .string(value)
|
|
return
|
|
} else if let value = try? container.decode(Int.self) {
|
|
self = .int(value)
|
|
return
|
|
} else if let value = try? container.decode(UInt.self) {
|
|
self = .uint(value)
|
|
return
|
|
} else if let value = try? container.decode(Double.self) {
|
|
self = .double(value)
|
|
return
|
|
} else if let value = try? container.decode(Bool.self) {
|
|
self = .bool(value)
|
|
return
|
|
} else if container.decodeNil() {
|
|
self = .null
|
|
return
|
|
} else {
|
|
// not a single value, try something else.
|
|
}
|
|
}
|
|
|
|
// try decoding object
|
|
if (try? decoder.container(keyedBy: Json.Key.self)) != nil {
|
|
|
|
let container = try decoder.singleValueContainer()
|
|
self = try .object(container.decode(Json.Object.self))
|
|
|
|
}
|
|
// try decoding array
|
|
else if (try? decoder.unkeyedContainer()) != nil {
|
|
|
|
let container = try decoder.singleValueContainer()
|
|
self = try .array(container.decode(Json.Array.self))
|
|
|
|
}
|
|
// default to null
|
|
else {
|
|
self = .null
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Json.Object: Encodable {
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: Json.Key.self)
|
|
for (key, member) in members {
|
|
if let codingKey = KeyedEncodingContainer<Json.Key>.Key(stringValue: key) {
|
|
try container.encode(member, forKey: codingKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
extension Json.Object: Decodable {
|
|
public init(from decoder: Decoder) throws {
|
|
members = [:]
|
|
let container = try decoder.container(keyedBy: Json.Key.self)
|
|
for key in container.allKeys {
|
|
let member = try container.decode(Json.Element.self, forKey: key)
|
|
members[key.stringValue] = member
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Json.Array: Encodable {
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.unkeyedContainer()
|
|
for element in elements {
|
|
try container.encode(element)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Json.Array: Decodable {
|
|
public init(from decoder: Decoder) throws {
|
|
elements = []
|
|
var container = try decoder.unkeyedContainer()
|
|
while !container.isAtEnd {
|
|
let element = try container.decode(Json.Element.self)
|
|
elements.append(element)
|
|
}
|
|
}
|
|
}
|
|
|
|
// init from encodable
|
|
public protocol EncodableConvertible {
|
|
init<T: Encodable>(value: T) throws
|
|
}
|
|
|
|
extension EncodableConvertible where Self: Decodable {
|
|
// converts encodable value to the self assuming they encode to the same JSON elements
|
|
public init<T: Encodable>(value: T) throws {
|
|
let encoder = JSONEncoder()
|
|
let data = try encoder.encode(value)
|
|
let decoder = JSONDecoder()
|
|
self = try decoder.decode(Self.self, from: data)
|
|
}
|
|
}
|
|
|
|
// convert to Decodable
|
|
// converts to decodable type assuming that self and T has the same JSON elements
|
|
public protocol DecodableConvertible {
|
|
func convert<T: Decodable>(to type: T.Type) throws -> T
|
|
}
|
|
|
|
extension DecodableConvertible where Self: Encodable {
|
|
public func convert<T: Decodable>(to type: T.Type) throws -> T {
|
|
let encoder = JSONEncoder()
|
|
let data = try encoder.encode(self)
|
|
let decoder = JSONDecoder()
|
|
let value = try decoder.decode(type, from: data)
|
|
return value
|
|
}
|
|
}
|
|
|
|
// default implementation
|
|
extension Json.Element: EncodableConvertible, DecodableConvertible {}
|
|
|
|
|
|
// A way to encode and decode NSError to Json
|
|
extension Json {
|
|
public struct NSError: Codable {
|
|
var domain: String
|
|
var code: Int
|
|
var userInfo: Json.Object
|
|
}
|
|
}
|
|
|
|
extension Json.NSError: JsonConvertible {
|
|
public init(_ nsError: Foundation.NSError) {
|
|
domain = nsError.domain
|
|
code = nsError.code
|
|
userInfo = Json.Object(
|
|
members: Dictionary(uniqueKeysWithValues: nsError.userInfo.map({ (key: String, value: Any) -> (String, Json.Element) in
|
|
// sometimes people extend String with Swift.Error, we don't want to encode that as NSError
|
|
if let value = value as? String {
|
|
return (key, Json.Element(any: value))
|
|
} else if let value = value as? Foundation.NSError {
|
|
// handle nexted NSError values
|
|
return (key, Json.Element(any: Json.NSError(value)))
|
|
} else {
|
|
return (key, Json.Element(any: value))
|
|
}
|
|
}))
|
|
)
|
|
}
|
|
|
|
|
|
public func nsError() -> NSError {
|
|
NSError(domain: domain, code: code, userInfo: Dictionary(uniqueKeysWithValues: userInfo.members.map({ (key: String, value: Json.Element) -> (String, Any) in
|
|
|
|
let resultValue = value.toAny()
|
|
|
|
// handle nested NSError values
|
|
if let object = resultValue as? [String: Any],
|
|
let domain = object["domain"] as? String,
|
|
let code = object["code"] as? Int,
|
|
let userInfo = object["userInfo"] as? [String: Any] {
|
|
return (key, Foundation.NSError(domain: domain, code: code, userInfo: userInfo) as Any)
|
|
} else {
|
|
return (key, resultValue)
|
|
}
|
|
})))
|
|
}
|
|
}
|
|
|
|
// Allows to convert any value to Json element
|
|
public protocol JsonConvertible {
|
|
func toJson() -> Json.Element
|
|
}
|
|
|
|
extension JsonConvertible where Self: Encodable {
|
|
public func toJson() -> Json.Element {
|
|
(try? Json.Element(value: self)) ?? .null
|
|
}
|
|
}
|
|
|
|
// Allows to convert to and from Swift.Any erased type
|
|
public protocol AnyConvertible {
|
|
func toAny() -> Any
|
|
init(any value: Any)
|
|
}
|
|
|
|
// Convert to and from elements, including nested JsonConvertible values
|
|
extension Json.Element: AnyConvertible {
|
|
public func toAny() -> Any {
|
|
switch self {
|
|
case .object(let object):
|
|
return Dictionary(uniqueKeysWithValues: object.members.map { (key: String, value: Json.Element) in
|
|
(key, value.toAny())
|
|
})
|
|
case .array(let array):
|
|
return array.elements.map { $0.toAny() }
|
|
case .string(let value):
|
|
return value as Any
|
|
case .int(let value):
|
|
return value as Any
|
|
case .uint(let value):
|
|
return value as Any
|
|
case .double(let value):
|
|
return value as Any
|
|
case .bool(let value):
|
|
return value as Any
|
|
case .null:
|
|
return NSNull() as Any
|
|
}
|
|
}
|
|
|
|
public init(any value: Any) {
|
|
if let value = value as? Int {
|
|
self = .int(value)
|
|
} else if let value = value as? UInt {
|
|
self = .uint(value)
|
|
} else if let value = value as? Double {
|
|
self = .double(value)
|
|
} else if let value = value as? Bool {
|
|
self = .bool(value)
|
|
} else if let value = value as? String {
|
|
self = .string(value)
|
|
} else if let value = value as? [Any] {
|
|
self = .array(Json.Array(elements: value.map(Json.Element.init(any:))))
|
|
} else if let value = value as? [String: Any] {
|
|
self = .object(Json.Object(members: Dictionary(uniqueKeysWithValues: value.map { ($0, Json.Element(any: $1)) } )))
|
|
} else if let value = value as? JsonConvertible {
|
|
self = value.toJson()
|
|
} else {
|
|
self = .null
|
|
}
|
|
}
|
|
}
|