Skip to content

Commit 90d6937

Browse files
Add JSValueDecoder
1 parent fd8d7d9 commit 90d6937

File tree

2 files changed

+338
-0
lines changed

2 files changed

+338
-0
lines changed

Diff for: Sources/JavaScriptKit/JSValueConstructible.swift

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
public protocol JSValueConstructible {
2+
static func construct(from value: JSValue) -> Self?
3+
}
4+
5+
extension Bool: JSValueConstructible {
6+
public static func construct(from value: JSValue) -> Bool? {
7+
value.boolean
8+
}
9+
}
10+
11+
extension String: JSValueConstructible {
12+
public static func construct(from value: JSValue) -> String? {
13+
value.string
14+
}
15+
}
16+
17+
extension Double: JSValueConstructible {
18+
public static func construct(from value: JSValue) -> Double? {
19+
guard let rawNumber: Int32 = value.number else { return nil }
20+
return Double(bitPattern: UInt64(bitPattern: Int64(rawNumber)))
21+
}
22+
}
23+
24+
extension Float: JSValueConstructible {
25+
public static func construct(from value: JSValue) -> Float? {
26+
guard let rawNumber: Int32 = value.number else { return nil }
27+
return Float(bitPattern: UInt32(bitPattern: rawNumber))
28+
}
29+
}
30+
31+
extension Int: JSValueConstructible {
32+
public static func construct(from value: JSValue) -> Self? {
33+
value.number.map(Self.init)
34+
}
35+
}
36+
37+
extension Int8: JSValueConstructible {
38+
public static func construct(from value: JSValue) -> Self? {
39+
value.number.map(Self.init)
40+
}
41+
}
42+
43+
extension Int16: JSValueConstructible {
44+
public static func construct(from value: JSValue) -> Self? {
45+
value.number.map(Self.init)
46+
}
47+
}
48+
49+
extension Int32: JSValueConstructible {
50+
public static func construct(from value: JSValue) -> Self? {
51+
value.number
52+
}
53+
}
54+
55+
extension Int64: JSValueConstructible {
56+
public static func construct(from value: JSValue) -> Self? {
57+
value.number.map(Self.init)
58+
}
59+
}
60+
61+
extension UInt: JSValueConstructible {
62+
public static func construct(from value: JSValue) -> Self? {
63+
value.number.map(Self.init)
64+
}
65+
}
66+
67+
extension UInt8: JSValueConstructible {
68+
public static func construct(from value: JSValue) -> Self? {
69+
value.number.map(Self.init)
70+
}
71+
}
72+
73+
extension UInt16: JSValueConstructible {
74+
public static func construct(from value: JSValue) -> Self? {
75+
value.number.map(Self.init)
76+
}
77+
}
78+
79+
extension UInt32: JSValueConstructible {
80+
public static func construct(from value: JSValue) -> Self? {
81+
value.number.map(Self.init)
82+
}
83+
}
84+
85+
extension UInt64: JSValueConstructible {
86+
public static func construct(from value: JSValue) -> Self? {
87+
value.number.map(Self.init)
88+
}
89+
}

Diff for: Sources/JavaScriptKit/JSValueDecoder.swift

+249
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
private struct _Decoder: Decoder {
2+
3+
fileprivate let node: JSValue
4+
5+
init(referencing node: JSValue, userInfo: [CodingUserInfoKey: Any], codingPath: [CodingKey] = []) {
6+
self.node = node
7+
self.userInfo = userInfo
8+
self.codingPath = codingPath
9+
}
10+
11+
let codingPath: [CodingKey]
12+
let userInfo: [CodingUserInfoKey : Any]
13+
14+
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
15+
guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) }
16+
return KeyedDecodingContainer(_KeyedDecodingContainer(decoder: self, ref: ref))
17+
}
18+
19+
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
20+
guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) }
21+
return _UnkeyedDecodingContainer(decoder: self, ref: ref)
22+
}
23+
24+
func singleValueContainer() throws -> SingleValueDecodingContainer {
25+
self
26+
}
27+
28+
func decoder(referencing node: JSValue, with key: CodingKey) -> _Decoder {
29+
_Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath + [key])
30+
}
31+
32+
func superDecoder(referencing node: JSValue) -> _Decoder {
33+
_Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath.dropLast())
34+
}
35+
36+
}
37+
38+
private enum Object {
39+
static let ref = JSObjectRef.global.get("Object").object!
40+
static func keys(_ object: JSObjectRef) -> [String] {
41+
let keysFn = ref.get("keys").function!
42+
let keys = keysFn(object).array!
43+
return keys.map { $0.string! }
44+
}
45+
}
46+
47+
private func _keyNotFound(at codingPath: [CodingKey], _ key: CodingKey) -> DecodingError {
48+
let description = "No value associated with key \(key) (\"\(key.stringValue)\")."
49+
let context = DecodingError.Context(codingPath: codingPath, debugDescription: description)
50+
return .keyNotFound(key, context)
51+
}
52+
53+
private func _typeMismatch(at codingPath: [CodingKey], _ type: Any.Type, reality: Any) -> DecodingError {
54+
let description = "Expected to decode \(type) but found \(reality) instead."
55+
let context = DecodingError.Context(codingPath: codingPath, debugDescription: description)
56+
return .typeMismatch(type, context)
57+
}
58+
59+
struct _JSCodingKey: CodingKey {
60+
var stringValue: String
61+
var intValue: Int?
62+
63+
init?(stringValue: String) {
64+
self.stringValue = stringValue
65+
self.intValue = nil
66+
}
67+
68+
init?(intValue: Int) {
69+
self.stringValue = "\(intValue)"
70+
self.intValue = intValue
71+
}
72+
73+
init(index: Int) {
74+
self.stringValue = "Index \(index)"
75+
self.intValue = index
76+
}
77+
78+
static let `super` = _JSCodingKey(stringValue: "super")!
79+
}
80+
81+
private struct _KeyedDecodingContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
82+
83+
private let decoder: _Decoder
84+
private let ref: JSObjectRef
85+
86+
var codingPath: [CodingKey] { return decoder.codingPath }
87+
var allKeys: [Key] {
88+
Object.keys(ref).compactMap(Key.init(stringValue: ))
89+
}
90+
91+
init(decoder: _Decoder, ref: JSObjectRef) {
92+
self.decoder = decoder
93+
self.ref = ref
94+
}
95+
96+
func _decode(forKey key: CodingKey) throws -> JSValue {
97+
let result = ref.get(key.stringValue)
98+
guard !result.isUndefined else {
99+
throw _keyNotFound(at: codingPath, key)
100+
}
101+
return result
102+
}
103+
104+
func _throwTypeMismatchIfNil<T>(forKey key: CodingKey, _ transform: (JSValue) -> T?) throws -> T {
105+
let jsValue = try _decode(forKey: key)
106+
guard let value = transform(jsValue) else {
107+
throw _typeMismatch(at: codingPath, T.self, reality: jsValue)
108+
}
109+
return value
110+
}
111+
112+
func contains(_ key: Key) -> Bool {
113+
!ref.get(key.stringValue).isUndefined
114+
}
115+
116+
func decodeNil(forKey key: Key) throws -> Bool {
117+
try _decode(forKey: key).isNull
118+
}
119+
120+
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: JSValueConstructible & Decodable {
121+
return try _throwTypeMismatchIfNil(forKey: key) { T.construct(from: $0) }
122+
}
123+
124+
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
125+
return try T(from: _decoder(forKey: key))
126+
}
127+
128+
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
129+
try _decoder(forKey: key).container(keyedBy: NestedKey.self)
130+
}
131+
132+
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
133+
try _decoder(forKey: key).unkeyedContainer()
134+
}
135+
136+
func superDecoder() throws -> Decoder {
137+
try _decoder(forKey: _JSCodingKey.super)
138+
}
139+
140+
func superDecoder(forKey key: Key) throws -> Decoder {
141+
try _decoder(forKey: key)
142+
}
143+
144+
func _decoder(forKey key: CodingKey) throws -> Decoder {
145+
let value = try _decode(forKey: key)
146+
return decoder.decoder(referencing: value, with: key)
147+
}
148+
}
149+
150+
private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer {
151+
var codingPath: [CodingKey] { decoder.codingPath }
152+
let count: Int?
153+
var isAtEnd: Bool { currentIndex >= count ?? 0 }
154+
var currentIndex: Int = 0
155+
156+
private var currentKey: CodingKey { return _JSCodingKey(index: currentIndex) }
157+
158+
let decoder: _Decoder
159+
let ref: JSObjectRef
160+
161+
init(decoder: _Decoder, ref: JSObjectRef) {
162+
self.decoder = decoder
163+
self.count = ref.length.number.map(Int.init)
164+
self.ref = ref
165+
}
166+
167+
mutating func _currentValue() -> JSValue {
168+
defer { currentIndex += 1 }
169+
return ref.get(currentIndex)
170+
}
171+
172+
mutating func _throwTypeMismatchIfNil<T>(_ transform: (JSValue) -> T?) throws -> T {
173+
let value = _currentValue()
174+
guard let jsValue = transform(value) else {
175+
throw _typeMismatch(at: codingPath, T.self, reality: value)
176+
}
177+
return jsValue
178+
}
179+
180+
mutating func decodeNil() throws -> Bool {
181+
return _currentValue().isNull
182+
}
183+
184+
mutating func decode<T>(_ type: T.Type) throws -> T where T: JSValueConstructible & Decodable {
185+
try _throwTypeMismatchIfNil { T.construct(from: $0) }
186+
}
187+
188+
mutating func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
189+
return try T(from: _decoder())
190+
}
191+
192+
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
193+
return try _decoder().container(keyedBy: NestedKey.self)
194+
}
195+
196+
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
197+
return try _decoder().unkeyedContainer()
198+
}
199+
200+
mutating func superDecoder() throws -> Decoder {
201+
_decoder()
202+
}
203+
204+
mutating func _decoder() -> Decoder {
205+
decoder.decoder(referencing: _currentValue(), with: currentKey)
206+
}
207+
}
208+
209+
210+
extension _Decoder: SingleValueDecodingContainer {
211+
func _throwTypeMismatchIfNil<T>(_ transform: (JSValue) -> T?) throws -> T {
212+
guard let jsValue = transform(node) else {
213+
throw _typeMismatch(at: codingPath, T.self, reality: node)
214+
}
215+
return jsValue
216+
}
217+
218+
func decodeNil() -> Bool {
219+
node.isNull
220+
}
221+
222+
func decode<T>(_ type: T.Type) throws -> T where T: JSValueConstructible & Decodable {
223+
try _throwTypeMismatchIfNil { T.construct(from: $0) }
224+
}
225+
226+
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
227+
let primitive = { (node: JSValue) -> T? in
228+
guard let constructibleType = type as? JSValueConstructible.Type else {
229+
return nil
230+
}
231+
return constructibleType.construct(from: node) as? T
232+
}
233+
return try primitive(node) ?? type.init(from: self)
234+
}
235+
}
236+
237+
public class JSValueDecoder {
238+
239+
public init() {}
240+
241+
public func decode<T>(
242+
_ type: T.Type = T.self,
243+
from value: JSValue,
244+
userInfo: [CodingUserInfoKey: Any] = [:]
245+
) throws -> T where T: Decodable {
246+
let decoder = _Decoder(referencing: value, userInfo: userInfo)
247+
return try T(from: decoder)
248+
}
249+
}

0 commit comments

Comments
 (0)