See More

#if !hasFeature(Embedded) private struct _Decoder: Decoder { fileprivate let node: JSValue init(referencing node: JSValue, userInfo: [CodingUserInfoKey: Any], codingPath: [CodingKey] = []) { self.node = node self.userInfo = userInfo self.codingPath = codingPath } let codingPath: [CodingKey] let userInfo: [CodingUserInfoKey: Any] func container(keyedBy _: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObject.self, reality: node) } return KeyedDecodingContainer(_KeyedDecodingContainer(decoder: self, ref: ref)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObject.self, reality: node) } return _UnkeyedDecodingContainer(decoder: self, ref: ref) } func singleValueContainer() throws -> SingleValueDecodingContainer { self } func decoder(referencing node: JSValue, with key: CodingKey) -> _Decoder { _Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath + [key]) } func superDecoder(referencing node: JSValue) -> _Decoder { _Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath.dropLast()) } } private enum Object { static func keys(_ object: JSObject) -> [String] { let keys = JSObject.constructor.keys!(object).array! return keys.map { $0.string! } } } private func _keyNotFound(at codingPath: [CodingKey], _ key: CodingKey) -> DecodingError { let description = "No value associated with key \(key) (\"\(key.stringValue)\")." let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) return .keyNotFound(key, context) } private func _typeMismatch(at codingPath: [CodingKey], _ type: Any.Type, reality: Any) -> DecodingError { let description = "Expected to decode \(type) but found \(reality) instead." let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) return .typeMismatch(type, context) } struct _JSCodingKey: CodingKey { var stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue intValue = nil } init?(intValue: Int) { stringValue = "\(intValue)" self.intValue = intValue } init(index: Int) { stringValue = "Index \(index)" intValue = index } static let `super` = _JSCodingKey(stringValue: "super")! } private struct _KeyedDecodingContainer: KeyedDecodingContainerProtocol { private let decoder: _Decoder private let ref: JSObject var codingPath: [CodingKey] { return decoder.codingPath } var allKeys: [Key] { Object.keys(ref).compactMap(Key.init(stringValue:)) } init(decoder: _Decoder, ref: JSObject) { self.decoder = decoder self.ref = ref } func _decode(forKey key: CodingKey) throws -> JSValue { let result = ref[key.stringValue] guard !result.isUndefined else { throw _keyNotFound(at: codingPath, key) } return result } func _throwTypeMismatchIfNil(forKey key: CodingKey, _ transform: (JSValue) -> T?) throws -> T { let jsValue = try _decode(forKey: key) guard let value = transform(jsValue) else { throw _typeMismatch(at: codingPath, T.self, reality: jsValue) } return value } func contains(_ key: Key) -> Bool { !ref[key.stringValue].isUndefined } func decodeNil(forKey key: Key) throws -> Bool { try _decode(forKey: key).isNull } func decode(_: T.Type, forKey key: Key) throws -> T where T: ConstructibleFromJSValue & Decodable { return try _throwTypeMismatchIfNil(forKey: key) { T.construct(from: $0) } } func decode(_: T.Type, forKey key: Key) throws -> T where T: Decodable { return try T(from: _decoder(forKey: key)) } func nestedContainer(keyedBy _: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try _decoder(forKey: key).container(keyedBy: NestedKey.self) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { try _decoder(forKey: key).unkeyedContainer() } func superDecoder() throws -> Decoder { try _decoder(forKey: _JSCodingKey.super) } func superDecoder(forKey key: Key) throws -> Decoder { try _decoder(forKey: key) } func _decoder(forKey key: CodingKey) throws -> Decoder { let value = try _decode(forKey: key) return decoder.decoder(referencing: value, with: key) } } private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer { var codingPath: [CodingKey] { decoder.codingPath } let count: Int? var isAtEnd: Bool { currentIndex >= count ?? 0 } var currentIndex: Int = 0 private var currentKey: CodingKey { return _JSCodingKey(index: currentIndex) } let decoder: _Decoder let ref: JSObject init(decoder: _Decoder, ref: JSObject) { self.decoder = decoder count = ref.length.number.map(Int.init) self.ref = ref } mutating func _currentValue() -> JSValue { defer { currentIndex += 1 } return ref[currentIndex] } mutating func _throwTypeMismatchIfNil(_ transform: (JSValue) -> T?) throws -> T { let value = _currentValue() guard let jsValue = transform(value) else { throw _typeMismatch(at: codingPath, T.self, reality: value) } return jsValue } mutating func decodeNil() throws -> Bool { return _currentValue().isNull } mutating func decode(_: T.Type) throws -> T where T: ConstructibleFromJSValue & Decodable { try _throwTypeMismatchIfNil { T.construct(from: $0) } } mutating func decode(_: T.Type) throws -> T where T: Decodable { return try T(from: _decoder()) } mutating func nestedContainer(keyedBy _: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey: CodingKey { return try _decoder().container(keyedBy: NestedKey.self) } mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { return try _decoder().unkeyedContainer() } mutating func superDecoder() throws -> Decoder { _decoder() } mutating func _decoder() -> Decoder { decoder.decoder(referencing: _currentValue(), with: currentKey) } } extension _Decoder: SingleValueDecodingContainer { func _throwTypeMismatchIfNil(_ transform: (JSValue) -> T?) throws -> T { guard let jsValue = transform(node) else { throw _typeMismatch(at: codingPath, T.self, reality: node) } return jsValue } func decodeNil() -> Bool { node.isNull } func decode(_: T.Type) throws -> T where T: ConstructibleFromJSValue & Decodable { try _throwTypeMismatchIfNil { T.construct(from: $0) } } func decode(_ type: T.Type) throws -> T where T: Decodable { let primitive = { (node: JSValue) -> T? in guard let constructibleType = type as? ConstructibleFromJSValue.Type else { return nil } return constructibleType.construct(from: node) as? T } return try primitive(node) ?? type.init(from: self) } } /// `JSValueDecoder` facilitates the decoding of JavaScript value into semantic `Decodable` types. public class JSValueDecoder { /// Initializes a new `JSValueDecoder`. public init() {} /// Decodes a top-level value of the given type from the given JavaScript value representation. /// /// - Parameter T: The type of the value to decode. /// - Parameter value: The `JSValue` to decode from. public func decode( _: T.Type = T.self, from value: JSValue, userInfo: [CodingUserInfoKey: Any] = [:] ) throws -> T where T: Decodable { let decoder = _Decoder(referencing: value, userInfo: userInfo) return try T(from: decoder) } } #endif