Skip to content

Commit c926720

Browse files
committed
SR-12275: JSONEncoder on Linux can't encode number JSON fragments
- JSONEncoder now encodes fragments by default to match Darwin. - JSONDecoder now allows fragments when decoding to match Darwin. - Update the following TestJSONEncoder tests now that fragments encode, these tests now pass on Darwin: test_encodingTopLevelSingleValueEnum test_encodingTopLevelSingleValueStruct test_encodingTopLevelSingleValueClass
1 parent 8fc7745 commit c926720

File tree

2 files changed

+47
-13
lines changed

2 files changed

+47
-13
lines changed

Sources/Foundation/JSONEncoder.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,8 @@ open class JSONEncoder {
238238
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
239239
}
240240

241-
if topLevel is NSNull {
242-
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as null JSON fragment."))
243-
} else if topLevel is NSNumber {
244-
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as number JSON fragment."))
245-
} else if topLevel is NSString {
246-
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as string JSON fragment."))
247-
}
241+
let writingOptions: JSONSerialization.WritingOptions = [JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue), .fragmentsAllowed]
248242

249-
let writingOptions = JSONSerialization.WritingOptions(rawValue: self.outputFormatting.rawValue)
250243
do {
251244
return try JSONSerialization.data(withJSONObject: topLevel, options: writingOptions)
252245
} catch {
@@ -1155,7 +1148,7 @@ open class JSONDecoder {
11551148
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
11561149
let topLevel: Any
11571150
do {
1158-
topLevel = try JSONSerialization.jsonObject(with: data)
1151+
topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
11591152
} catch {
11601153
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
11611154
}

Tests/Foundation/Tests/TestJSONEncoder.swift

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@ struct TopLevelObjectWrapper<T: Codable & Equatable>: Codable, Equatable {
2121

2222
class TestJSONEncoder : XCTestCase {
2323

24+
// MARK: - Encoding Top-Level fragments
25+
func test_encodingTopLevelFragments() {
26+
27+
func _testFragment<T: Codable & Equatable>(value: T, fragment: String) {
28+
let data: Data
29+
let payload: String
30+
31+
do {
32+
data = try JSONEncoder().encode(value)
33+
payload = try XCTUnwrap(String.init(decoding: data, as: UTF8.self))
34+
XCTAssertEqual(fragment, payload)
35+
} catch {
36+
XCTFail("Failed to encode \(T.self) to JSON: \(error)")
37+
return
38+
}
39+
do {
40+
let decodedValue = try JSONDecoder().decode(T.self, from: data)
41+
XCTAssertEqual(value, decodedValue)
42+
} catch {
43+
XCTFail("Failed to decode \(payload) to \(T.self): \(error)")
44+
}
45+
}
46+
47+
_testFragment(value: 2, fragment: "2")
48+
_testFragment(value: false, fragment: "false")
49+
_testFragment(value: true, fragment: "true")
50+
_testFragment(value: Float(1), fragment: "1")
51+
_testFragment(value: Double(2), fragment: "2")
52+
_testFragment(value: Decimal(Double.leastNormalMagnitude), fragment: "0.0000000000000000000000000000000000000000000000000002225073858507201792")
53+
_testFragment(value: "test", fragment: "\"test\"")
54+
let v: Int? = nil
55+
_testFragment(value: v, fragment: "null")
56+
}
57+
2458
// MARK: - Encoding Top-Level Empty Types
2559
func test_encodingTopLevelEmptyStruct() {
2660
let empty = EmptyStruct()
@@ -34,20 +68,20 @@ class TestJSONEncoder : XCTestCase {
3468

3569
// MARK: - Encoding Top-Level Single-Value Types
3670
func test_encodingTopLevelSingleValueEnum() {
37-
_testEncodeFailure(of: Switch.off)
38-
_testEncodeFailure(of: Switch.on)
71+
_testRoundTrip(of: Switch.off)
72+
_testRoundTrip(of: Switch.on)
3973

4074
_testRoundTrip(of: TopLevelArrayWrapper(Switch.off))
4175
_testRoundTrip(of: TopLevelArrayWrapper(Switch.on))
4276
}
4377

4478
func test_encodingTopLevelSingleValueStruct() {
45-
_testEncodeFailure(of: Timestamp(3141592653))
79+
_testRoundTrip(of: Timestamp(3141592653))
4680
_testRoundTrip(of: TopLevelArrayWrapper(Timestamp(3141592653)))
4781
}
4882

4983
func test_encodingTopLevelSingleValueClass() {
50-
_testEncodeFailure(of: Counter())
84+
_testRoundTrip(of: Counter())
5185
_testRoundTrip(of: TopLevelArrayWrapper(Counter()))
5286
}
5387

@@ -492,6 +526,11 @@ class TestJSONEncoder : XCTestCase {
492526
}
493527
}
494528

529+
func test_codingOfNil() {
530+
let x: Int? = nil
531+
test_codingOf(value: x, toAndFrom: "null")
532+
}
533+
495534
func test_codingOfInt8() {
496535
test_codingOf(value: Int8(-42), toAndFrom: "-42")
497536
}
@@ -1364,6 +1403,7 @@ fileprivate struct JSON: Equatable {
13641403
extension TestJSONEncoder {
13651404
static var allTests: [(String, (TestJSONEncoder) -> () throws -> Void)] {
13661405
return [
1406+
("test_encodingTopLevelFragments", test_encodingTopLevelFragments),
13671407
("test_encodingTopLevelEmptyStruct", test_encodingTopLevelEmptyStruct),
13681408
("test_encodingTopLevelEmptyClass", test_encodingTopLevelEmptyClass),
13691409
("test_encodingTopLevelSingleValueEnum", test_encodingTopLevelSingleValueEnum),
@@ -1393,6 +1433,7 @@ extension TestJSONEncoder {
13931433
("test_nestedContainerCodingPaths", test_nestedContainerCodingPaths),
13941434
("test_superEncoderCodingPaths", test_superEncoderCodingPaths),
13951435
("test_codingOfBool", test_codingOfBool),
1436+
("test_codingOfNil", test_codingOfNil),
13961437
("test_codingOfInt8", test_codingOfInt8),
13971438
("test_codingOfUInt8", test_codingOfUInt8),
13981439
("test_codingOfInt16", test_codingOfInt16),

0 commit comments

Comments
 (0)