@@ -59,12 +59,318 @@ extension AnyCodable: Equatable {
5959 return lhs == rhs
6060 case let ( lhs as String , rhs as String ) :
6161 return lhs == rhs
62- case ( let lhs as [ String : AnyCodable ] , let rhs as [ String : AnyCodable ] ) :
63- return lhs == rhs
64- case ( let lhs as [ AnyCodable ] , let rhs as [ AnyCodable ] ) :
65- return lhs == rhs
6662 case ( let lhs as CoatyUUID , let rhs as CoatyUUID ) :
6763 return lhs == rhs
64+ case let ( lhs as CoatyObject , rhs as CoatyObject ) :
65+ return AnyCodable . deepEquals ( lhs, rhs)
66+ case ( let lhs as [ String : AnyCodable ] , let rhs as [ String : AnyCodable ] ) :
67+ return AnyCodable . deepEquals ( lhs, rhs)
68+ case ( let lhs as [ Any ] , let rhs as [ Any ] ) :
69+ return AnyCodable . deepEquals ( lhs, rhs)
70+ default :
71+ return false
72+ }
73+ }
74+ }
75+
76+ extension AnyCodable {
77+ /// - Note: Internal for Internal use in framework only
78+ ///
79+ /// Utitlity function to pack Any as AnyCodable while using the most specific type for the value.
80+ ///
81+ /// - Parameters:
82+ /// - value: value to be packed as AnyCodable
83+ /// - Returns: AnyCodable of the value (with the most specific type); nil if the value should never be packed as AnyCodable
84+ internal static func _getAnyAsAnyCodable( _ value: Any ) -> AnyCodable ? {
85+ if let v = value as? Bool {
86+ return AnyCodable ( booleanLiteral: v)
87+ } else if let v = value as? Int {
88+ return AnyCodable ( integerLiteral: v)
89+ } else if let v = value as? Int8 {
90+ return AnyCodable ( v)
91+ } else if let v = value as? Int16 {
92+ return AnyCodable ( v)
93+ } else if let v = value as? Int32 {
94+ return AnyCodable ( v)
95+ } else if let v = value as? Int64 {
96+ return AnyCodable ( v)
97+ } else if let v = value as? UInt {
98+ return AnyCodable ( v)
99+ } else if let v = value as? UInt8 {
100+ return AnyCodable ( v)
101+ } else if let v = value as? UInt16 {
102+ return AnyCodable ( v)
103+ } else if let v = value as? UInt32 {
104+ return AnyCodable ( v)
105+ } else if let v = value as? UInt64 {
106+ return AnyCodable ( v)
107+ } else if let v = value as? Float {
108+ return AnyCodable ( v)
109+ } else if let v = value as? Double {
110+ return AnyCodable ( v)
111+ } else if let v = value as? String {
112+ return AnyCodable ( stringLiteral: v)
113+ } else if let v = value as? CoatyUUID {
114+ return AnyCodable ( v)
115+ } else if let v = value as? CoatyObject {
116+ return AnyCodable ( v)
117+ } else if let v = value as? [ Any ] {
118+ let result = v. map { any -> AnyCodable in
119+ if let nonNil = AnyCodable . _getAnyAsAnyCodable ( any) {
120+ return nonNil
121+ } else {
122+ return AnyCodable ( nil )
123+ }
124+ }
125+ return AnyCodable ( result)
126+ } else if let v = value as? [ String : Any ] {
127+ var result = [ String: AnyCodable] ( )
128+ for key in v. keys {
129+ if let unwrappedValue = AnyCodable . _getAnyAsAnyCodable ( v [ key] !) {
130+ result [ key] = unwrappedValue
131+ } else {
132+ result [ key] = nil
133+ }
134+ }
135+ return AnyCodable ( result)
136+ } else if let v = value as? AnyCodable {
137+ return AnyCodable . _getAnyAsAnyCodable ( v. value)
138+ } else {
139+ return nil
140+ }
141+ }
142+ }
143+
144+ extension AnyCodable {
145+ /// - Note: Internal for internal use in framework only
146+ ///
147+ /// Determines whether two Arrays [Any] are structurally equal
148+ /// (aka. deep equal) according to a recursive equality algorithm.
149+ ///
150+ /// - Parameters:
151+ /// - lhs: an array of type [Any]
152+ /// - rhs: an array of type [Any]
153+ internal static func deepEquals( _ lhs: [ Any ] , _ rhs: [ Any ] ) -> Bool {
154+ // All elements have to be of type AnyCodable to perform element-wise comparisons
155+ let lhsAsCodables = lhs. map { AnyCodable . _getAnyAsAnyCodable ( $0) }
156+ let rhsAsCodables = rhs. map { AnyCodable . _getAnyAsAnyCodable ( $0) }
157+
158+ return lhsAsCodables == rhsAsCodables
159+ }
160+
161+ /// - Note: Internal for internal use in framework only
162+ ///
163+ /// Determines whether two Dictionaries [String: AnyCodable] are structurally equal
164+ /// (aka. deep equal) according to a recursive equality algorithm.
165+ ///
166+ /// - Parameters:
167+ /// - lhs: a dictionary of type [String: AnyCodable]
168+ /// - rhs: a dictionary of type [String: AnyCodable]
169+ /// - Returns: true if two dictionaries are structurally equal
170+ internal static func deepEquals( _ lhs: [ String : AnyCodable ] , _ rhs: [ String : AnyCodable ] ) -> Bool {
171+ if lhs. keys. count != rhs. keys. count {
172+ return false
173+ }
174+
175+ for prop in lhs. keys {
176+ if let lhsVal = lhs [ prop] ,
177+ let rhsVal = rhs [ prop] ,
178+ lhsVal != rhsVal {
179+ return false
180+ }
181+ }
182+
183+ return true
184+ }
185+
186+ /// - Note: Internal for internal use in framework only
187+ ///
188+ /// Determines whether two CoatyObjects are structurally equal
189+ /// (aka. deep equal) according to a recursive equality algorithm.
190+ ///
191+ /// - Parameters:
192+ /// - lhs: a Coaty object
193+ /// - rhs: a Coaty object
194+ /// - Returns: true if the two objects are structurally equal (property names and values must be the same)
195+ internal static func deepEquals( _ lhs: CoatyObject , _ rhs: CoatyObject ) -> Bool {
196+ let lhsProperties = AnyCodable . getDictionaryOfProperties ( from: Mirror ( reflecting: lhs) )
197+ let rhsProperties = AnyCodable . getDictionaryOfProperties ( from: Mirror ( reflecting: rhs) )
198+
199+ if lhsProperties. keys. count != rhsProperties. keys. count {
200+ return false
201+ }
202+
203+ for prop in lhsProperties. keys {
204+ if let lhsVal = lhsProperties [ prop] ,
205+ let rhsVal = rhsProperties [ prop] ,
206+ lhsVal != rhsVal {
207+ return false
208+ }
209+ }
210+
211+ return true
212+ }
213+ }
214+
215+ extension AnyCodable {
216+ /// Checks if a JavaScript value (usually an object or array) contains
217+ /// other values. Primitive value types (number, string, boolean, null,undefined) contain
218+ /// only the identical value. Object properties match if all the key-value
219+ /// pairs of the specified object are contained in them. Array properties
220+ /// match if all the specified array elements are contained in them.
221+ ///
222+ /// The general principle is that the contained object must match the containing object
223+ /// as to structure and data contents recursively on all levels, possibly after discarding
224+ /// some non-matching array elements or object key/value pairs from the containing object.
225+ /// But remember that the order of array elements is not significant when doing a containment match,
226+ /// and duplicate array elements are effectively considered only once.
227+ ///
228+ /// As a special exception to the general principle that the structures must match, an
229+ /// array on *toplevel* may contain a primitive value:
230+ /// ```ts
231+ /// contains([1, 2, 3], [3]) => true
232+ /// contains([1, 2, 3], 3) => true
233+ /// ```
234+ ///
235+ /// @param a a JavaScript value containing another value
236+ /// @param b a JavaScript value to be contained in another value
237+ internal static func deepContains( _ a: AnyCodable , _ b: AnyCodable ) -> Bool {
238+
239+ let bAsAnyCodable = AnyCodable . _getAnyAsAnyCodable ( b) !
240+ let aAsAnyCodable = AnyCodable . _getAnyAsAnyCodable ( a) !
241+ return AnyCodable . _deepContains ( aAsAnyCodable, bAsAnyCodable, true )
242+ }
243+
244+ internal static func _deepContains( _ x: AnyCodable , _ y: AnyCodable , _ isTopLevel: Bool ) -> Bool {
245+ if let xValues = x. value as? [ AnyCodable ] {
246+ if let yValues = y. value as? [ AnyCodable ] {
247+ return yValues. allSatisfy { yv -> Bool in
248+ return xValues. contains { xv -> Bool in
249+ AnyCodable . _deepContains ( xv, yv, false )
250+ }
251+ }
252+ } else {
253+ // Special exception: check containment of a primitive array element on toplevel
254+ if isTopLevel {
255+ return xValues. contains { xv -> Bool in
256+ xv == y
257+ }
258+ }
259+ return false
260+ }
261+
262+ }
263+
264+ if let xAsObject = x. value as? CoatyObject {
265+ if let yAsObject = y. value as? CoatyObject {
266+ let xProperties = AnyCodable . getDictionaryOfProperties ( from: Mirror ( reflecting: xAsObject) )
267+ let yProperties = AnyCodable . getDictionaryOfProperties ( from: Mirror ( reflecting: yAsObject) )
268+
269+ return xProperties. keys. allSatisfy { xk -> Bool in
270+ if yProperties. index ( forKey: xk) == nil {
271+ return false
272+ }
273+ return AnyCodable . _deepContains ( xProperties [ xk] !, yProperties [ xk] !, false )
274+ }
275+ } else {
276+ return false
277+ }
278+ }
279+
280+ return x == y
281+ }
282+ }
283+
284+ extension AnyCodable {
285+
286+ /// - Note: Internal for internal use in framework only
287+ ///
288+ /// Checks if a value is included on toplevel in the given
289+ /// operand array of values which may be primitive types (number, string, boolean, null)
290+ /// or object types compared using the == equality operator.
291+ ///
292+ /// - Parameters:
293+ /// - lhs: an array containing another value on toplevel
294+ /// - rhs: a value to be contained on toplevel in an array
295+ /// - Returns: true if rhs is contained in lhs
296+ internal static func deepIncludes( _ lhs: AnyCodable , _ rhs: AnyCodable ) -> Bool {
297+ // First argument must be an array of Any values
298+ guard let lhsAsArray = lhs. value as? [ Any ] else {
299+ return false
300+ }
301+
302+ let lhsAsCodables = lhsAsArray. compactMap { AnyCodable . _getAnyAsAnyCodable ( $0) }
303+
304+ return lhsAsCodables. contains { element -> Bool in
305+ return element == rhs
306+ }
307+ }
308+ }
309+
310+ extension AnyCodable {
311+ /// - Note: Internal for interal use in framework only
312+ ///
313+ /// Recursively get a dictionary of all properties as [String: AnyCodable] associated with a given mirror of an object
314+ ///
315+ /// - Parameters:
316+ /// - mirror: mirror of the object that properties need to be extracted
317+ /// - Returns: a dictionary of all properties associated with the object (only those properties that can be represented as AnyCodable)
318+ internal static func getDictionaryOfProperties( from mirror: Mirror ) -> [ String : AnyCodable ] {
319+ var result = [ String: AnyCodable] ( )
320+
321+ for child in mirror. children {
322+ result [ child. label!] = AnyCodable . _getAnyAsAnyCodable ( child. value)
323+ }
324+
325+ guard let superMirror = mirror. superclassMirror else {
326+ return result
327+ }
328+
329+ return result. merging ( AnyCodable . getDictionaryOfProperties ( from: superMirror) ) { ( _, new) in
330+ new
331+ }
332+ }
333+ }
334+
335+ extension AnyCodable : Comparable {
336+ public static func < ( lhs: AnyCodable , rhs: AnyCodable ) -> Bool {
337+ switch ( lhs. value, rhs. value) {
338+ case is ( Void , Void ) :
339+ return false
340+ case let ( lhs as Bool , rhs as Bool ) :
341+ return ( lhs == false ) && ( rhs == true )
342+ case let ( lhs as Int , rhs as Int ) :
343+ return lhs < rhs
344+ case let ( lhs as Int8 , rhs as Int8 ) :
345+ return lhs < rhs
346+ case let ( lhs as Int16 , rhs as Int16 ) :
347+ return lhs < rhs
348+ case let ( lhs as Int32 , rhs as Int32 ) :
349+ return lhs < rhs
350+ case let ( lhs as Int64 , rhs as Int64 ) :
351+ return lhs < rhs
352+ case let ( lhs as UInt , rhs as UInt ) :
353+ return lhs < rhs
354+ case let ( lhs as UInt8 , rhs as UInt8 ) :
355+ return lhs < rhs
356+ case let ( lhs as UInt16 , rhs as UInt16 ) :
357+ return lhs < rhs
358+ case let ( lhs as UInt32 , rhs as UInt32 ) :
359+ return lhs < rhs
360+ case let ( lhs as UInt64 , rhs as UInt64 ) :
361+ return lhs < rhs
362+ case let ( lhs as Float , rhs as Float ) :
363+ return lhs < rhs
364+ case let ( lhs as Double , rhs as Double ) :
365+ return lhs < rhs
366+ case let ( lhs as String , rhs as String ) :
367+ switch lhs. localizedCompare ( rhs) {
368+ case . orderedAscending:
369+ return true
370+ default :
371+ return false
372+ }
373+ // CoatyUUID (description)
68374 default :
69375 return false
70376 }
@@ -96,3 +402,4 @@ extension AnyCodable: CustomDebugStringConvertible {
96402}
97403
98404extension AnyCodable : ExpressibleByNilLiteral , ExpressibleByBooleanLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByStringLiteral , ExpressibleByArrayLiteral , ExpressibleByDictionaryLiteral { }
405+
0 commit comments