Skip to content

Commit 355497e

Browse files
Switch JSONValue's number case back to Double
This reverts the public API change of fd1e91c whilst preserving the internal MessagePack-related motivation for that commit.
1 parent 96799cb commit 355497e

File tree

6 files changed

+51
-55
lines changed

6 files changed

+51
-55
lines changed
Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Foundation
22

3-
/// Like ``JSONValue``, but provides an additional case named `extra`, which allows you to support additional types of data. It's used as a common base for the implementations of ``JSONValue`` and ``WireValue``, and for converting between them.
4-
internal indirect enum ExtendedJSONValue<Extra> {
3+
/// Like ``JSONValue``, but provides a flexible `number` case and an additional case named `extra`, which allows you to support additional types of data. It's used as a common base for the implementations of ``JSONValue`` and ``WireValue``, and for converting between them.
4+
internal indirect enum ExtendedJSONValue<Number, Extra> {
55
case object([String: Self])
66
case array([Self])
77
case string(String)
8-
case number(NSNumber)
8+
case number(Number)
99
case bool(Bool)
1010
case null
1111
case extra(Extra)
@@ -17,12 +17,12 @@ internal extension ExtendedJSONValue {
1717
/// Creates an `ExtendedJSONValue` from an object.
1818
///
1919
/// The rules for what `deserialized` will accept are the same as those of `JSONValue.init(jsonSerializationOutput)`, with one addition: any nonsupported values are passed to the `createExtraValue` function, and the result of this function will be used to create an `ExtendedJSONValue` of case `.extra`.
20-
init(deserialized: Any, createExtraValue: (Any) -> Extra) {
20+
init(deserialized: Any, createNumberValue: (NSNumber) -> Number, createExtraValue: (Any) -> Extra) {
2121
switch deserialized {
2222
case let dictionary as [String: Any]:
23-
self = .object(dictionary.mapValues { .init(deserialized: $0, createExtraValue: createExtraValue) })
23+
self = .object(dictionary.mapValues { .init(deserialized: $0, createNumberValue: createNumberValue, createExtraValue: createExtraValue) })
2424
case let array as [Any]:
25-
self = .array(array.map { .init(deserialized: $0, createExtraValue: createExtraValue) })
25+
self = .array(array.map { .init(deserialized: $0, createNumberValue: createNumberValue, createExtraValue: createExtraValue) })
2626
case let string as String:
2727
self = .string(string)
2828
case let number as NSNumber:
@@ -32,7 +32,7 @@ internal extension ExtendedJSONValue {
3232
} else if number === kCFBooleanFalse {
3333
self = .bool(false)
3434
} else {
35-
self = .number(number)
35+
self = .number(createNumberValue(number))
3636
}
3737
case is NSNull:
3838
self = .null
@@ -44,16 +44,16 @@ internal extension ExtendedJSONValue {
4444
/// Converts an `ExtendedJSONValue` to an object.
4545
///
4646
/// The contract for what this will return are the same as those of `JSONValue.toJSONSerializationInputElement`, with one addition: any values in the input of case `.extra` will be passed to the `serializeExtraValue` function, and the result of this function call will be inserted into the output object.
47-
func serialized(serializeExtraValue: (Extra) -> Any) -> Any {
47+
func serialized(serializeNumberValue: (Number) -> Any, serializeExtraValue: (Extra) -> Any) -> Any {
4848
switch self {
4949
case let .object(underlying):
50-
underlying.mapValues { $0.serialized(serializeExtraValue: serializeExtraValue) }
50+
underlying.mapValues { $0.serialized(serializeNumberValue: serializeNumberValue, serializeExtraValue: serializeExtraValue) }
5151
case let .array(underlying):
52-
underlying.map { $0.serialized(serializeExtraValue: serializeExtraValue) }
52+
underlying.map { $0.serialized(serializeNumberValue: serializeNumberValue, serializeExtraValue: serializeExtraValue) }
5353
case let .string(underlying):
5454
underlying
5555
case let .number(underlying):
56-
underlying
56+
serializeNumberValue(underlying)
5757
case let .bool(underlying):
5858
underlying
5959
case .null:
@@ -64,37 +64,30 @@ internal extension ExtendedJSONValue {
6464
}
6565
}
6666

67-
internal extension ExtendedJSONValue where Extra == Never {
68-
var serialized: Any {
69-
// swiftlint:disable:next trailing_closure
70-
serialized(serializeExtraValue: { _ in })
71-
}
72-
}
73-
7467
// MARK: - Transforming the extra data
7568

7669
internal extension ExtendedJSONValue {
77-
/// Converts this `ExtendedJSONValue<Extra>` to an `ExtendedJSONValue<NewExtra>` using a given transformation.
78-
func map<NewExtra, Failure>(_ transform: @escaping (Extra) throws(Failure) -> NewExtra) throws(Failure) -> ExtendedJSONValue<NewExtra> {
70+
/// Converts this `ExtendedJSONValue<Number, Extra>` to an `ExtendedJSONValue<NewNumber, NewExtra>` using given transformations.
71+
func map<NewNumber, NewExtra, Failure>(number transformNumber: @escaping (Number) throws(Failure) -> NewNumber, extra transformExtra: @escaping (Extra) throws(Failure) -> NewExtra) throws(Failure) -> ExtendedJSONValue<NewNumber, NewExtra> {
7972
switch self {
8073
case let .object(underlying):
8174
try .object(underlying.ablyLiveObjects_mapValuesWithTypedThrow { value throws(Failure) in
82-
try value.map(transform)
75+
try value.map(number: transformNumber, extra: transformExtra)
8376
})
8477
case let .array(underlying):
8578
try .array(underlying.map { element throws(Failure) in
86-
try element.map(transform)
79+
try element.map(number: transformNumber, extra: transformExtra)
8780
})
8881
case let .string(underlying):
8982
.string(underlying)
9083
case let .number(underlying):
91-
.number(underlying)
84+
try .number(transformNumber(underlying))
9285
case let .bool(underlying):
9386
.bool(underlying)
9487
case .null:
9588
.null
9689
case let .extra(extra):
97-
try .extra(transform(extra))
90+
try .extra(transformExtra(extra))
9891
}
9992
}
10093
}

Sources/AblyLiveObjects/Utility/JSONValue.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public indirect enum JSONValue: Sendable, Equatable {
2929
case object([String: JSONValue])
3030
case array([JSONValue])
3131
case string(String)
32-
case number(NSNumber)
32+
case number(Double)
3333
case bool(Bool)
3434
case null
3535

@@ -63,7 +63,7 @@ public indirect enum JSONValue: Sendable, Equatable {
6363
}
6464

6565
/// If this `JSONValue` has case `number`, this returns the associated value. Else, it returns `nil`.
66-
public var numberValue: NSNumber? {
66+
public var numberValue: Double? {
6767
if case let .number(numberValue) = self {
6868
numberValue
6969
} else {
@@ -110,13 +110,13 @@ extension JSONValue: ExpressibleByStringLiteral {
110110

111111
extension JSONValue: ExpressibleByIntegerLiteral {
112112
public init(integerLiteral value: Int) {
113-
self = .number(value as NSNumber)
113+
self = .number(Double(value))
114114
}
115115
}
116116

117117
extension JSONValue: ExpressibleByFloatLiteral {
118118
public init(floatLiteral value: Double) {
119-
self = .number(value as NSNumber)
119+
self = .number(value)
120120
}
121121
}
122122

@@ -136,8 +136,7 @@ internal extension JSONValue {
136136
/// - The result of serializing an array or dictionary using `JSONSerialization`
137137
/// - Some nested element of the result of serializing such an array or dictionary
138138
init(jsonSerializationOutput: Any) {
139-
// swiftlint:disable:next trailing_closure
140-
let extended = ExtendedJSONValue<Never>(deserialized: jsonSerializationOutput, createExtraValue: { deserializedExtraValue in
139+
let extended = ExtendedJSONValue<Double, Never>(deserialized: jsonSerializationOutput, createNumberValue: { $0.doubleValue }, createExtraValue: { deserializedExtraValue in
141140
// JSONSerialization is not conforming to our assumptions; our assumptions are probably wrong. Either way, bring this loudly to our attention instead of trying to carry on
142141
preconditionFailure("JSONValue(jsonSerializationOutput:) was given unsupported value \(deserializedExtraValue)")
143142
})
@@ -152,7 +151,7 @@ internal extension JSONValue {
152151
/// - All cases: An object which we can put inside an array or dictionary that we ask `JSONSerialization` to serialize
153152
/// - Additionally, if case `object` or `array`: An object which we can ask `JSONSerialization` to serialize
154153
var toJSONSerializationInputElement: Any {
155-
toExtendedJSONValue.serialized
154+
toExtendedJSONValue.serialized(serializeNumberValue: { $0 as NSNumber }, serializeExtraValue: { _ in })
156155
}
157156
}
158157

@@ -226,7 +225,7 @@ internal extension [JSONValue] {
226225
// MARK: - Conversion to/from ExtendedJSONValue
227226

228227
internal extension JSONValue {
229-
init(extendedJSONValue: ExtendedJSONValue<Never>) {
228+
init(extendedJSONValue: ExtendedJSONValue<Double, Never>) {
230229
switch extendedJSONValue {
231230
case let .object(underlying):
232231
self = .object(underlying.mapValues { .init(extendedJSONValue: $0) })
@@ -243,7 +242,7 @@ internal extension JSONValue {
243242
}
244243
}
245244

246-
var toExtendedJSONValue: ExtendedJSONValue<Never> {
245+
var toExtendedJSONValue: ExtendedJSONValue<Double, Never> {
247246
switch self {
248247
case let .object(underlying):
249248
.object(underlying.mapValues(\.toExtendedJSONValue))

Sources/AblyLiveObjects/Utility/WireValue.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Foundation
33

44
/// A wire value that can be represents the kinds of data that we expect to find inside a deserialized wire object received from `_AblyPluginSupportPrivate`, or which we may put inside a serialized wire object that we send to `_AblyPluginSupportPrivate`.
55
///
6-
/// Its cases are a superset of those of ``JSONValue``, adding a further `data` case for binary data (we expect to be able to send and receive binary data in the case where ably-cocoa is using the MessagePack format).
6+
/// Its cases are a superset of those of ``JSONValue``, adding a further `data` case for binary data (we expect to be able to send and receive binary data in the case where ably-cocoa is using the MessagePack format). Also, its `number` case is `NSNumber` instead of `Double`, to allow us to communicate to ably-cocoa's MessagePack encoder that it should encode certain values (e.g. enums) as integers, not doubles.
77
internal indirect enum WireValue: Sendable, Equatable {
88
case object([String: WireValue])
99
case array([WireValue])
@@ -122,8 +122,7 @@ internal extension WireValue {
122122
///
123123
/// Specifically, `pluginSupportData` can be a value that was passed to `LiveObjectsPlugin.decodeObjectMessage:…`.
124124
init(pluginSupportData: Any) {
125-
// swiftlint:disable:next trailing_closure
126-
let extendedJSONValue = ExtendedJSONValue<ExtraValue>(deserialized: pluginSupportData, createExtraValue: { deserializedExtraValue in
125+
let extendedJSONValue = ExtendedJSONValue<NSNumber, ExtraValue>(deserialized: pluginSupportData, createNumberValue: { $0 }, createExtraValue: { deserializedExtraValue in
127126
// We support binary data (used for MessagePack format) in addition to JSON values
128127
if let data = deserializedExtraValue as? Data {
129128
return .data(data)
@@ -150,8 +149,7 @@ internal extension WireValue {
150149
///
151150
/// Used by `[String: WireValue].toPluginSupportDataDictionary`.
152151
var toPluginSupportData: Any {
153-
// swiftlint:disable:next trailing_closure
154-
toExtendedJSONValue.serialized(serializeExtraValue: { extendedValue in
152+
toExtendedJSONValue.serialized(serializeNumberValue: { $0 }, serializeExtraValue: { extendedValue in
155153
switch extendedValue {
156154
case let .data(data):
157155
data
@@ -176,7 +174,7 @@ internal extension WireValue {
176174
case data(Data)
177175
}
178176

179-
init(extendedJSONValue: ExtendedJSONValue<ExtraValue>) {
177+
init(extendedJSONValue: ExtendedJSONValue<NSNumber, ExtraValue>) {
180178
switch extendedJSONValue {
181179
case let .object(underlying):
182180
self = .object(underlying.mapValues { .init(extendedJSONValue: $0) })
@@ -198,7 +196,7 @@ internal extension WireValue {
198196
}
199197
}
200198

201-
var toExtendedJSONValue: ExtendedJSONValue<ExtraValue> {
199+
var toExtendedJSONValue: ExtendedJSONValue<NSNumber, ExtraValue> {
202200
switch self {
203201
case let .object(underlying):
204202
.object(underlying.mapValues(\.toExtendedJSONValue))
@@ -223,8 +221,9 @@ internal extension WireValue {
223221
internal extension WireValue {
224222
/// Converts a `JSONValue` to its corresponding `WireValue`.
225223
init(jsonValue: JSONValue) {
226-
// swiftlint:disable:next array_init
227-
self.init(extendedJSONValue: jsonValue.toExtendedJSONValue.map { (extra: Never) in extra })
224+
self.init(extendedJSONValue: jsonValue.toExtendedJSONValue.map(number: { (number: Double) -> NSNumber in
225+
number as NSNumber
226+
}, extra: { (extra: Never) in extra }))
228227
}
229228

230229
enum ConversionError: Error {
@@ -236,12 +235,14 @@ internal extension WireValue {
236235
/// - Throws: `ConversionError.dataCannotBeConvertedToJSONValue` if `WireValue` represents binary data.
237236
var toJSONValue: JSONValue {
238237
get throws(ARTErrorInfo) {
239-
let neverExtended = try toExtendedJSONValue.map { extra throws(ARTErrorInfo) -> Never in
238+
let neverExtended = try toExtendedJSONValue.map(number: { (number: NSNumber) throws(ARTErrorInfo) -> Double in
239+
number.doubleValue
240+
}, extra: { (extra: ExtraValue) throws(ARTErrorInfo) -> Never in
240241
switch extra {
241242
case .data:
242243
throw ConversionError.dataCannotBeConvertedToJSONValue.toARTErrorInfo()
243244
}
244-
}
245+
})
245246

246247
return .init(extendedJSONValue: neverExtended)
247248
}

Tests/AblyLiveObjectsTests/JS Integration Tests/ObjectsHelper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ final class ObjectsHelper: Sendable {
451451
]
452452

453453
if let number {
454-
opBody["data"] = .object(["number": .number(NSNumber(value: number))])
454+
opBody["data"] = .object(["number": .number(number)])
455455
}
456456

457457
if let objectId {
@@ -467,7 +467,7 @@ final class ObjectsHelper: Sendable {
467467
[
468468
"operation": .string(Actions.counterInc.stringValue),
469469
"objectId": .string(objectId),
470-
"data": .object(["number": .number(NSNumber(value: number))]),
470+
"data": .object(["number": .number(number)]),
471471
]
472472
}
473473

Tests/AblyLiveObjectsTests/JS Integration Tests/ObjectsIntegrationTests.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ private let objectsFixturesChannel = "objects_fixtures"
131131

132132
// MARK: - Top-level fixtures (ported from JS objects.test.js)
133133

134+
// The value of JS's `Number.MAX_SAFE_INTEGER` — the maximum integer that a `Double` can represent exactly.
135+
private let maxSafeInteger = Double((1 << 53) - 1)
136+
134137
// Primitive key data fixture used across multiple test scenarios
135138
// liveMapValue field contains the value as LiveMapValue for use in map operations
136139
private let primitiveKeyData: [(key: String, data: [String: JSONValue], liveMapValue: LiveMapValue)] = [
@@ -156,13 +159,13 @@ private let primitiveKeyData: [(key: String, data: [String: JSONValue], liveMapV
156159
),
157160
(
158161
key: "maxSafeIntegerKey",
159-
data: ["number": .number(.init(value: Int.max))],
160-
liveMapValue: .number(Double(Int.max))
162+
data: ["number": .number(maxSafeInteger)],
163+
liveMapValue: .number(maxSafeInteger)
161164
),
162165
(
163166
key: "negativeMaxSafeIntegerKey",
164-
data: ["number": .number(.init(value: -Int.max))],
165-
liveMapValue: .number(-Double(Int.max))
167+
data: ["number": .number(-maxSafeInteger)],
168+
liveMapValue: .number(-maxSafeInteger)
166169
),
167170
(
168171
key: "numberKey",
@@ -854,7 +857,7 @@ private struct ObjectsIntegrationTests {
854857
let expectedData = Data(base64Encoded: bytesString)
855858
#expect(try mapObj.get(key: key)?.dataValue == expectedData, "Check map \"\(mapKey)\" has correct value for \"\(key)\" key")
856859
} else if let numberValue = data["number"]?.numberValue {
857-
#expect(try mapObj.get(key: key)?.numberValue == numberValue.doubleValue, "Check map \"\(mapKey)\" has correct value for \"\(key)\" key")
860+
#expect(try mapObj.get(key: key)?.numberValue == numberValue, "Check map \"\(mapKey)\" has correct value for \"\(key)\" key")
858861
} else if let stringValue = data["string"]?.stringValue {
859862
#expect(try mapObj.get(key: key)?.stringValue == stringValue, "Check map \"\(mapKey)\" has correct value for \"\(key)\" key")
860863
} else if let boolValue = data["boolean"]?.boolValue {
@@ -1070,7 +1073,7 @@ private struct ObjectsIntegrationTests {
10701073
let expectedData = Data(base64Encoded: bytesString)
10711074
#expect(try mapValue.get(key: "value")?.dataValue == expectedData, "Check root has correct value for \"\(keyData.key)\" key after MAP_SET op")
10721075
} else if let numberValue = keyData.data["number"]?.numberValue {
1073-
#expect(try mapValue.get(key: "value")?.numberValue == numberValue.doubleValue, "Check root has correct value for \"\(keyData.key)\" key after MAP_SET op")
1076+
#expect(try mapValue.get(key: "value")?.numberValue == numberValue, "Check root has correct value for \"\(keyData.key)\" key after MAP_SET op")
10741077
} else if let stringValue = keyData.data["string"]?.stringValue {
10751078
#expect(try mapValue.get(key: "value")?.stringValue == stringValue, "Check root has correct value for \"\(keyData.key)\" key after MAP_SET op")
10761079
} else if let boolValue = keyData.data["boolean"]?.boolValue {
@@ -2047,7 +2050,7 @@ private struct ObjectsIntegrationTests {
20472050
}
20482051
} else if let numberValue = keyData.data["number"] {
20492052
if case let .number(expectedNumber) = numberValue {
2050-
#expect(try #require(root.get(key: keyData.key)?.numberValue) == expectedNumber.doubleValue, "Check root has correct value for \"\(keyData.key)\" key after OBJECT_SYNC has ended and buffered operations are applied")
2053+
#expect(try #require(root.get(key: keyData.key)?.numberValue) == expectedNumber, "Check root has correct value for \"\(keyData.key)\" key after OBJECT_SYNC has ended and buffered operations are applied")
20512054
}
20522055
} else if let boolValue = keyData.data["boolean"] {
20532056
if case let .bool(expectedBool) = boolValue {
@@ -2347,7 +2350,7 @@ private struct ObjectsIntegrationTests {
23472350
}
23482351
} else if let numberValue = keyData.data["number"] {
23492352
if case let .number(expectedNumber) = numberValue {
2350-
#expect(try #require(root.get(key: keyData.key)?.numberValue) == expectedNumber.doubleValue, "Check root has correct value for \"\(keyData.key)\" key after OBJECT_SYNC has ended and buffered operations are applied")
2353+
#expect(try #require(root.get(key: keyData.key)?.numberValue) == expectedNumber, "Check root has correct value for \"\(keyData.key)\" key after OBJECT_SYNC has ended and buffered operations are applied")
23512354
}
23522355
} else if let boolValue = keyData.data["boolean"] {
23532356
if case let .bool(expectedBool) = boolValue {

Tests/AblyLiveObjectsTests/ObjectCreationHelpersTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ struct ObjectCreationHelpersTests {
6565
#expect(deserializedInitialValue == [
6666
"map": [
6767
// RTO11f4a
68-
"semantics": .number(ObjectsMapSemantics.lww.rawValue as NSNumber),
68+
"semantics": .number(Double(ObjectsMapSemantics.lww.rawValue)),
6969
"entries": [
7070
// RTO11f4c1a
7171
"mapRef": [

0 commit comments

Comments
 (0)