Skip to content

Commit fca4773

Browse files
committed
add option to sort maps by key
so it does not introduce breaking changes this flag is set as default to true Signed-off-by: Goncalo Frade <goncalofrade08@gmail.com>
1 parent 04ccff1 commit fca4773

File tree

3 files changed

+56
-15
lines changed

3 files changed

+56
-15
lines changed

Sources/CBOREncoder.swift

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,26 @@ extension CBOR {
136136
res.reserveCapacity(1 + map.count * (MemoryLayout<A>.size + MemoryLayout<B>.size + 2))
137137
res = map.count.encode(options: options)
138138
res[0] = res[0] | 0b101_00000
139-
for (k, v) in map {
140-
res.append(contentsOf: k.encode(options: options))
141-
res.append(contentsOf: v.encode(options: options))
139+
140+
if options.shouldSortMapKeys {
141+
let sortedKeysWithEncodedKeys = map.keys.map {
142+
(encoded: $0.encode(options: options), key: $0)
143+
}.sorted(by: {
144+
$0.encoded.lexicographicallyPrecedes($1.encoded)
145+
})
146+
147+
sortedKeysWithEncodedKeys.forEach { keyTuple in
148+
res.append(contentsOf: keyTuple.encoded)
149+
guard let value = map[keyTuple.key] else {
150+
return
151+
}
152+
res.append(contentsOf: value.encode(options: options))
153+
}
154+
} else {
155+
for (k, v) in map {
156+
res.append(contentsOf: k.encode(options: options))
157+
res.append(contentsOf: v.encode(options: options))
158+
}
142159
}
143160
return res
144161
}
@@ -442,16 +459,24 @@ extension CBOR {
442459
if options.forbidNonStringMapKeys {
443460
try ensureStringKey(A.self)
444461
}
445-
let sortedKeysWithEncodedKeys = map.keys.map {
446-
(encoded: $0.encode(options: options), key: $0)
447-
}.sorted(by: {
448-
$0.encoded.lexicographicallyPrecedes($1.encoded)
449-
})
450-
451-
try sortedKeysWithEncodedKeys.forEach { keyTuple in
452-
res.append(contentsOf: keyTuple.encoded)
453-
let encodedVal = try encodeAny(map[keyTuple.key]!, options: options)
454-
res.append(contentsOf: encodedVal)
462+
if options.shouldSortMapKeys {
463+
let sortedKeysWithEncodedKeys = map.keys.map {
464+
(encoded: $0.encode(options: options), key: $0)
465+
}.sorted(by: {
466+
$0.encoded.lexicographicallyPrecedes($1.encoded)
467+
})
468+
469+
try sortedKeysWithEncodedKeys.forEach { keyTuple in
470+
res.append(contentsOf: keyTuple.encoded)
471+
let encodedVal = try encodeAny(map[keyTuple.key]!, options: options)
472+
res.append(contentsOf: encodedVal)
473+
}
474+
} else {
475+
for (k, v) in map {
476+
res.append(contentsOf: k.encode(options: options))
477+
let encodedVal = try encodeAny(v, options: options)
478+
res.append(contentsOf: encodedVal)
479+
}
455480
}
456481
}
457482
}

Sources/CBOROptions.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ public struct CBOROptions {
44
let forbidNonStringMapKeys: Bool
55
/// The maximum number of nested items, inclusive, to decode. A maximum set to 0 dissallows anything other than top-level primitives.
66
let maximumDepth: Int
7+
let shouldSortMapKeys: Bool
78

89
public init(
910
useStringKeys: Bool = false,
1011
dateStrategy: DateStrategy = .taggedAsEpochTimestamp,
1112
forbidNonStringMapKeys: Bool = false,
12-
maximumDepth: Int = .max
13+
maximumDepth: Int = .max,
14+
shouldShortMapKeys: Bool = true
1315
) {
1416
self.useStringKeys = useStringKeys
1517
self.dateStrategy = dateStrategy
1618
self.forbidNonStringMapKeys = forbidNonStringMapKeys
1719
self.maximumDepth = maximumDepth
20+
self.shouldSortMapKeys = shouldShortMapKeys
1821
}
1922

2023
func toCodableEncoderOptions() -> CodableCBOREncoder._Options {

Tests/CBOREncoderTests.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class CBOREncoderTests: XCTestCase {
102102
"a": 1,
103103
"b": [2, 3]
104104
]
105-
let encodedMapToAny = try! CBOR.encodeMap(mapToAny)
105+
let encodedMapToAny = try! CBOR.encodeMap(mapToAny, options: .init(shouldShortMapKeys: true))
106106
XCTAssertEqual(encodedMapToAny, [0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03])
107107

108108
let mapToAnyWithIntKeys: [Int: Any] = [
@@ -113,6 +113,19 @@ class CBOREncoderTests: XCTestCase {
113113
XCTAssertEqual(err as! CBOREncoderError, CBOREncoderError.nonStringKeyInMap)
114114
}
115115
}
116+
117+
func testEncodeSortedMaps() {
118+
XCTAssertEqual(CBOR.encode(Dictionary<Int, Int>()), [0xa0])
119+
120+
let encoded = CBOR.encode([3: 4, 1: 2])
121+
XCTAssert(encoded == [0xa2, 0x01, 0x02, 0x03, 0x04])
122+
123+
let arr1: CBOR = [1]
124+
let arr2: CBOR = [2,3]
125+
let nestedEnc: [UInt8] = CBOR.encode(["b": arr2, "a": arr1])
126+
let encodedAFirst: [UInt8] = [0xa2, 0x61, 0x61, 0x81, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03]
127+
XCTAssert(nestedEnc == encodedAFirst)
128+
}
116129

117130
func testEncodeTagged() {
118131
let bignum: [UInt8] = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] // 2**64

0 commit comments

Comments
 (0)