Skip to content

Commit 6ababc1

Browse files
authored
SWIFT-344 Add equalsIgnoreKeyOrder (#51)
1 parent b8e5602 commit 6ababc1

File tree

3 files changed

+126
-15
lines changed

3 files changed

+126
-15
lines changed

Sources/SwiftBSON/BSONDocument.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,38 @@ extension BSONDocument: BSONValue {
517517
extension BSONDocument: CustomStringConvertible {
518518
public var description: String { self.toExtendedJSONString() }
519519
}
520+
521+
extension BSONDocument {
522+
/**
523+
* Returns whether this `BSONDocument` contains exactly the same key/value pairs as the provided `BSONDocument`,
524+
* regardless of the order of the keys.
525+
*
526+
* Warning: This method is much less efficient than checking for regular equality since the document is internally
527+
* ordered.
528+
*
529+
* - Parameters:
530+
* - other: a `BSONDocument` to compare this document with.
531+
*
532+
* - Returns: a `Bool` indicating whether the two documents are equal.
533+
*/
534+
public func equalsIgnoreKeyOrder(_ other: BSONDocument) -> Bool {
535+
guard self.count == other.count else {
536+
return false
537+
}
538+
539+
for (k, v) in self {
540+
let otherValue = other[k]
541+
if case let (.document(docA), .document(docB)?) = (v, otherValue) {
542+
guard docA.equalsIgnoreKeyOrder(docB) else {
543+
return false
544+
}
545+
continue
546+
}
547+
guard v == otherValue else {
548+
return false
549+
}
550+
}
551+
552+
return true
553+
}
554+
}

Tests/SwiftBSONTests/CommonTestUtils.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,11 @@ public func sortedEqual(_ expectedValue: BSONDocument?) -> Predicate<BSONDocumen
5656
return PredicateResult(status: .fail, message: msg)
5757
}
5858

59-
let matches = expected.sortedEquals(actual)
59+
let matches = expected.equalsIgnoreKeyOrder(actual)
6060
return PredicateResult(status: PredicateStatus(bool: matches), message: msg)
6161
}
6262
}
6363

64-
extension BSONDocument {
65-
/// Compares two `BSONDocument`s and returns true if they have the same key/value pairs in them.
66-
public func sortedEquals(_ other: BSONDocument) -> Bool {
67-
let keys = self.keys.sorted()
68-
let otherKeys = other.keys.sorted()
69-
70-
// first compare keys, because rearrangeDoc will discard any that don't exist in `expected`
71-
expect(keys).to(equal(otherKeys))
72-
73-
let rearranged = rearrangeDoc(other, toLookLike: self)
74-
return self == rearranged
75-
}
76-
}
77-
7864
/// Given two documents, returns a copy of the input document with all keys that *don't*
7965
/// exist in `standard` removed, and with all matching keys put in the same order they
8066
/// appear in `standard`.

Tests/SwiftBSONTests/DocumentTests.swift

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,96 @@ final class DocumentTests: BSONTestCase {
237237
.to(equal(["hi": true, "hello": "hi", "cat": 2] as BSONDocument))
238238
}
239239

240+
func testEqualsIgnoreKeyOrder() throws {
241+
// basic comparisons
242+
let doc1: BSONDocument = ["foo": "bar", "bread": 1]
243+
let doc2: BSONDocument = ["foo": "bar", "bread": 1]
244+
expect(doc1.equalsIgnoreKeyOrder(doc2)).to(equal(true))
245+
246+
let doc3: BSONDocument = ["foo": "bar", "bread": 1]
247+
let doc4: BSONDocument = ["foo": "foo", "bread": 2]
248+
expect(doc3.equalsIgnoreKeyOrder(doc4)).to(equal(false))
249+
250+
// more complex comparisons
251+
let a: BSONDocument = [
252+
"string": "test string",
253+
"true": true,
254+
"false": false,
255+
"int": 25,
256+
"int32": .int32(5),
257+
"int64": .int64(10),
258+
"double": .double(15),
259+
"regex": .regex(BSONRegularExpression(pattern: "^abc", options: "imx")),
260+
"decimal128": .decimal128(try! BSONDecimal128("1.2E+10")),
261+
"minkey": .minKey,
262+
"maxkey": .maxKey,
263+
"date": .datetime(Date(timeIntervalSince1970: 500.004)),
264+
"timestamp": .timestamp(BSONTimestamp(timestamp: 5, inc: 10)),
265+
"nesteddoc": ["a": 1, "b": 2, "c": false, "d": [3, 4]],
266+
"oid": .objectID(try! BSONObjectID("507f1f77bcf86cd799439011")),
267+
"array1": [1, 2],
268+
"array2": ["string1", "string2"],
269+
"null": .null,
270+
"code": .code(BSONCode(code: "console.log('hi');")),
271+
"nestedarray": [[1, 2], [.int32(3), .int32(4)]],
272+
"codewscope": .codeWithScope(BSONCodeWithScope(code: "console.log(x);", scope: ["x": 2]))
273+
]
274+
275+
let b: BSONDocument = [
276+
"true": true,
277+
"int": 25,
278+
"int32": .int32(5),
279+
"int64": .int64(10),
280+
"string": "test string",
281+
"double": .double(15),
282+
"decimal128": .decimal128(try! BSONDecimal128("1.2E+10")),
283+
"minkey": .minKey,
284+
"date": .datetime(Date(timeIntervalSince1970: 500.004)),
285+
"timestamp": .timestamp(BSONTimestamp(timestamp: 5, inc: 10)),
286+
"nestedarray": [[1, 2], [.int32(3), .int32(4)]],
287+
"codewscope": .codeWithScope(BSONCodeWithScope(code: "console.log(x);", scope: ["x": 2])),
288+
"nesteddoc": ["b": 2, "a": 1, "d": [3, 4], "c": false],
289+
"oid": .objectID(try! BSONObjectID("507f1f77bcf86cd799439011")),
290+
"false": false,
291+
"regex": .regex(BSONRegularExpression(pattern: "^abc", options: "imx")),
292+
"array1": [1, 2],
293+
"array2": ["string1", "string2"],
294+
"null": .null,
295+
"code": .code(BSONCode(code: "console.log('hi');")),
296+
"maxkey": .maxKey
297+
]
298+
299+
// comparing two documents with the same key-value pairs in different order should return true
300+
expect(a.equalsIgnoreKeyOrder(b)).to(equal(true))
301+
302+
let c: BSONDocument = [
303+
"true": true,
304+
"int": 52,
305+
"int32": .int32(15),
306+
"int64": .int64(100),
307+
"string": "this is different string",
308+
"double": .double(15),
309+
"decimal128": .decimal128(try! BSONDecimal128("1.2E+10")),
310+
"minkey": .minKey,
311+
"date": .datetime(Date(timeIntervalSince1970: 500.004)),
312+
"array1": [1, 2],
313+
"timestamp": .timestamp(BSONTimestamp(timestamp: 5, inc: 10)),
314+
"nestedarray": [[1, 2], [.int32(3), .int32(4)]],
315+
"codewscope": .codeWithScope(BSONCodeWithScope(code: "console.log(x);", scope: ["x": 2])),
316+
"nesteddoc": ["1": 1, "2": 2, "3": true, "4": [5, 6]],
317+
"oid": .objectID(try! BSONObjectID("507f1f77bcf86cd799439011")),
318+
"false": false,
319+
"regex": .regex(BSONRegularExpression(pattern: "^abc", options: "imx")),
320+
"array2": ["string3", "string2", "string1"],
321+
"null": .null,
322+
"code": .code(BSONCode(code: "console.log('hi');")),
323+
"maxkey": .maxKey
324+
]
325+
326+
// comparing two documents with same keys but different values should return false
327+
expect(a.equalsIgnoreKeyOrder(c)).to(equal(false))
328+
}
329+
240330
func testRawBSON() throws {
241331
let doc = try BSONDocument(fromJSON: "{\"a\":[{\"$numberInt\":\"10\"}]}")
242332
let fromRawBSON = try BSONDocument(fromBSON: doc.buffer)

0 commit comments

Comments
 (0)