@@ -20,3 +20,104 @@ extension Dictionary: JSValue where Key == String, Value == JSValue {}
2020// convenience aliases
2121public typealias JSObject = [ String : JSValue ]
2222public typealias JSArray = [ JSValue ]
23+
24+ extension Dictionary where Key == String , Value == JSValue {
25+ public func getValue( _ key: String ) -> JSValue ? {
26+ return self [ key]
27+ }
28+
29+ public func getString( _ key: String ) -> String ? {
30+ return self [ key] as? String
31+ }
32+
33+ public func getBool( _ key: String ) -> Bool ? {
34+ return self [ key] as? Bool
35+ }
36+
37+ public func getInt( _ key: String ) -> Int ? {
38+ return self [ key] as? Int
39+ }
40+
41+ public func getFloat( _ key: String ) -> Float ? {
42+ if let floatValue = self [ key] as? Float {
43+ return floatValue
44+ } else if let doubleValue = self [ key] as? Double {
45+ return Float ( doubleValue)
46+ }
47+ return nil
48+ }
49+
50+ public func getDouble( _ key: String ) -> Double ? {
51+ return self [ key] as? Double
52+ }
53+
54+ public func getArray( _ key: String ) -> JSArray ? {
55+ return self [ key] as? JSArray
56+ }
57+
58+ public func getObject( _ key: String ) -> JSObject ? {
59+ return self [ key] as? JSObject
60+ }
61+ }
62+
63+ /*
64+ Simply casting objects from foundation class clusters (such as __NSArrayM)
65+ doesn't work with the JSValue protocol and will always fail. So we need to
66+ recursively and explicitly convert each value in the dictionary.
67+ */
68+ public enum JSTypes { }
69+ extension JSTypes {
70+ public static func coerceDictionaryToJSObject(
71+ _ dictionary: NSDictionary ? , formattingDatesAsStrings: Bool = false
72+ ) -> JSObject ? {
73+ return coerceToJSValue ( dictionary, formattingDates: formattingDatesAsStrings) as? JSObject
74+ }
75+
76+ public static func coerceDictionaryToJSObject(
77+ _ dictionary: [ AnyHashable : Any ] ? , formattingDatesAsStrings: Bool = false
78+ ) -> JSObject ? {
79+ return coerceToJSValue ( dictionary, formattingDates: formattingDatesAsStrings) as? JSObject
80+ }
81+ }
82+
83+ private let dateStringFormatter = ISO8601DateFormatter ( )
84+
85+ // We need a large switch statement because we have a lot of types.
86+ // swiftlint:disable:next cyclomatic_complexity
87+ private func coerceToJSValue( _ value: Any ? , formattingDates: Bool ) -> JSValue ? {
88+ guard let value = value else {
89+ return nil
90+ }
91+ switch value {
92+ case let stringValue as String :
93+ return stringValue
94+ case let numberValue as NSNumber :
95+ return numberValue
96+ case let boolValue as Bool :
97+ return boolValue
98+ case let intValue as Int :
99+ return intValue
100+ case let floatValue as Float :
101+ return floatValue
102+ case let doubleValue as Double :
103+ return doubleValue
104+ case let dateValue as Date :
105+ if formattingDates {
106+ return dateStringFormatter. string ( from: dateValue)
107+ }
108+ return dateValue
109+ case let nullValue as NSNull :
110+ return nullValue
111+ case let arrayValue as NSArray :
112+ return arrayValue. compactMap { coerceToJSValue ( $0, formattingDates: formattingDates) }
113+ case let dictionaryValue as NSDictionary :
114+ let keys = dictionaryValue. allKeys. compactMap { $0 as? String }
115+ var result : JSObject = [ : ]
116+ for key in keys {
117+ result [ key] = coerceToJSValue ( dictionaryValue [ key] , formattingDates: formattingDates)
118+ }
119+ return result
120+ default :
121+ return nil
122+ }
123+ }
0 commit comments