Skip to content

Commit 2b2a70f

Browse files
authored
Don’t encode NaN and Infinity (#13)
1 parent 92f2f45 commit 2b2a70f

File tree

7 files changed

+311
-2
lines changed

7 files changed

+311
-2
lines changed

Sources/PureSwiftJSONCoding/Encoding/JSONKeyedEncodingContainer.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,22 @@ struct JSONKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol
3434
}
3535

3636
mutating func encode(_ value: Double, forKey key: Self.Key) throws {
37+
guard !value.isNaN, !value.isInfinite else {
38+
throw EncodingError.invalidValue(value, .init(
39+
codingPath: self.codingPath + [key],
40+
debugDescription: "Unable to encode Double.\(value) directly in JSON."))
41+
}
42+
3743
try encodeFloatingPoint(value, key: key)
3844
}
3945

4046
mutating func encode(_ value: Float, forKey key: Self.Key) throws {
47+
guard !value.isNaN, !value.isInfinite else {
48+
throw EncodingError.invalidValue(value, .init(
49+
codingPath: self.codingPath + [key],
50+
debugDescription: "Unable to encode Float.\(value) directly in JSON."))
51+
}
52+
4153
try encodeFloatingPoint(value, key: key)
4254
}
4355

Sources/PureSwiftJSONCoding/Encoding/JSONSingleValueEncodingContainer.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,22 @@ struct JSONSingleValueEncodingContainer: SingleValueEncodingContainer {
6161
}
6262

6363
mutating func encode(_ value: Float) throws {
64+
guard !value.isNaN, !value.isInfinite else {
65+
throw EncodingError.invalidValue(value, .init(
66+
codingPath: self.codingPath,
67+
debugDescription: "Unable to encode Float.\(value) directly in JSON."))
68+
}
69+
6470
try encodeFloatingPoint(value)
6571
}
6672

6773
mutating func encode(_ value: Double) throws {
74+
guard !value.isNaN, !value.isInfinite else {
75+
throw EncodingError.invalidValue(value, .init(
76+
codingPath: self.codingPath,
77+
debugDescription: "Unable to encode Double.\(value) directly in JSON."))
78+
}
79+
6880
try encodeFloatingPoint(value)
6981
}
7082

Sources/PureSwiftJSONCoding/Encoding/JSONUnkeyedEncodingContainer.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,22 @@ struct JSONUnkeyedEncodingContainer: UnkeyedEncodingContainer {
3434
}
3535

3636
mutating func encode(_ value: Double) throws {
37+
guard !value.isNaN, !value.isInfinite else {
38+
throw EncodingError.invalidValue(value, .init(
39+
codingPath: self.codingPath + [ArrayKey(index: count)],
40+
debugDescription: "Unable to encode Double.\(value) directly in JSON."))
41+
}
42+
3743
try encodeFloatingPoint(value)
3844
}
3945

4046
mutating func encode(_ value: Float) throws {
47+
guard !value.isNaN, !value.isInfinite else {
48+
throw EncodingError.invalidValue(value, .init(
49+
codingPath: self.codingPath + [ArrayKey(index: count)],
50+
debugDescription: "Unable to encode Float.\(value) directly in JSON."))
51+
}
52+
4153
try encodeFloatingPoint(value)
4254
}
4355

Tests/JSONCodingTests/Encoding/JSONEncoderTests.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,68 @@ class JSONEncoderTests: XCTestCase {
7373
}
7474
}
7575

76+
func testEncodeDoubleNAN() {
77+
do {
78+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(Double.nan)
79+
XCTFail("Did not expect to have a result: \(result)")
80+
}
81+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
82+
XCTAssert(value.isNaN) // expected
83+
XCTAssertEqual(context.codingPath.count, 0)
84+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.nan directly in JSON.")
85+
}
86+
catch {
87+
XCTFail("Unexpected error: \(error)")
88+
}
89+
}
90+
91+
func testEncodeDoubleInf() {
92+
do {
93+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(Double.infinity)
94+
XCTFail("Did not expect to have a result: \(result)")
95+
}
96+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
97+
XCTAssert(value.isInfinite) // expected
98+
XCTAssertEqual(context.codingPath.count, 0)
99+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.inf directly in JSON.")
100+
}
101+
catch {
102+
// missing expected catch
103+
XCTFail("Unexpected error: \(error)")
104+
}
105+
}
106+
107+
func testEncodeFloatNAN() {
108+
do {
109+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(Float.nan)
110+
XCTFail("Did not expect to have a result: \(result)")
111+
}
112+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
113+
XCTAssert(value.isNaN) // expected
114+
XCTAssertEqual(context.codingPath.count, 0)
115+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.nan directly in JSON.")
116+
}
117+
catch {
118+
XCTFail("Unexpected error: \(error)")
119+
}
120+
}
121+
122+
func testEncodeFloatInf() {
123+
do {
124+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(Float.infinity)
125+
XCTFail("Did not expect to have a result: \(result)")
126+
}
127+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
128+
XCTAssert(value.isInfinite) // expected
129+
XCTAssertEqual(context.codingPath.count, 0)
130+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.inf directly in JSON.")
131+
}
132+
catch {
133+
// missing expected catch
134+
XCTFail("Unexpected error: \(error)")
135+
}
136+
}
137+
76138
func testEncodeQuote() {
77139
do {
78140
let result = try PureSwiftJSONCoding.JSONEncoder().encode("\"")

Tests/JSONCodingTests/Encoding/JSONKeyedEncodingContainerTests.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,85 @@ import XCTest
44

55
class JSONKeyedEncodingContainerTests: XCTestCase {
66

7+
// MARK: - NaN & Inf -
8+
9+
struct DoubleBox: Encodable {
10+
let number: Double
11+
}
12+
13+
func testEncodeDoubleNAN() {
14+
do {
15+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(DoubleBox(number: .nan))
16+
XCTFail("Did not expect to have a result: \(result)")
17+
}
18+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
19+
XCTAssert(value.isNaN) // expected
20+
XCTAssertEqual(context.codingPath.count, 1)
21+
XCTAssertEqual(context.codingPath.first?.stringValue, "number")
22+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.nan directly in JSON.")
23+
}
24+
catch {
25+
XCTFail("Unexpected error: \(error)")
26+
}
27+
}
28+
29+
func testEncodeDoubleInf() {
30+
do {
31+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(DoubleBox(number: .infinity))
32+
XCTFail("Did not expect to have a result: \(result)")
33+
}
34+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
35+
XCTAssert(value.isInfinite) // expected
36+
XCTAssertEqual(context.codingPath.count, 1)
37+
XCTAssertEqual(context.codingPath.first?.stringValue, "number")
38+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.inf directly in JSON.")
39+
}
40+
catch {
41+
// missing expected catch
42+
XCTFail("Unexpected error: \(error)")
43+
}
44+
}
45+
46+
struct FloatBox: Encodable {
47+
let number: Float
48+
}
49+
50+
func testEncodeFloatNAN() {
51+
do {
52+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(FloatBox(number: .nan))
53+
XCTFail("Did not expect to have a result: \(result)")
54+
}
55+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
56+
XCTAssert(value.isNaN) // expected
57+
XCTAssertEqual(context.codingPath.count, 1)
58+
XCTAssertEqual(context.codingPath.first?.stringValue, "number")
59+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.nan directly in JSON.")
60+
}
61+
catch {
62+
XCTFail("Unexpected error: \(error)")
63+
}
64+
}
65+
66+
func testEncodeFloatInf() {
67+
do {
68+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(FloatBox(number: .infinity))
69+
XCTFail("Did not expect to have a result: \(result)")
70+
}
71+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
72+
XCTAssert(value.isInfinite) // expected
73+
XCTAssertEqual(context.codingPath.count, 1)
74+
XCTAssertEqual(context.codingPath.first?.stringValue, "number")
75+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.inf directly in JSON.")
76+
}
77+
catch {
78+
// missing expected catch
79+
XCTFail("Unexpected error: \(error)")
80+
}
81+
}
82+
83+
84+
// MARK: - Nested Container -
85+
786
func testNestedKeyedContainer() {
887
struct Object: Encodable {
988
let firstName: String

Tests/JSONCodingTests/Encoding/JSONUnkeyedEncodingContainerTests.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,95 @@ import XCTest
44

55
class JSONUnkeyedEncodingContainerTests: XCTestCase {
66

7+
// MARK: - NaN & Inf -
8+
9+
struct DoubleInArrayBox: Encodable {
10+
let number: Double
11+
12+
func encode(to encoder: Encoder) throws {
13+
var container = encoder.unkeyedContainer()
14+
try container.encode(number)
15+
}
16+
}
17+
18+
func testEncodeDoubleNAN() {
19+
do {
20+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(DoubleInArrayBox(number: .nan))
21+
XCTFail("Did not expect to have a result: \(result)")
22+
}
23+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
24+
XCTAssert(value.isNaN) // expected
25+
XCTAssertEqual(context.codingPath.count, 1)
26+
XCTAssertEqual(context.codingPath.first?.stringValue, "Index 0")
27+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.nan directly in JSON.")
28+
}
29+
catch {
30+
XCTFail("Unexpected error: \(error)")
31+
}
32+
}
33+
34+
func testEncodeDoubleInf() {
35+
do {
36+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(DoubleInArrayBox(number: .infinity))
37+
XCTFail("Did not expect to have a result: \(result)")
38+
}
39+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
40+
XCTAssert(value.isInfinite) // expected
41+
XCTAssertEqual(context.codingPath.count, 1)
42+
XCTAssertEqual(context.codingPath.first?.stringValue, "Index 0")
43+
XCTAssertEqual(context.debugDescription, "Unable to encode Double.inf directly in JSON.")
44+
}
45+
catch {
46+
// missing expected catch
47+
XCTFail("Unexpected error: \(error)")
48+
}
49+
}
50+
51+
struct FloatInArrayBox: Encodable {
52+
let number: Float
53+
54+
func encode(to encoder: Encoder) throws {
55+
var container = encoder.unkeyedContainer()
56+
try container.encode(number)
57+
}
58+
}
59+
60+
func testEncodeFloatNAN() {
61+
do {
62+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(FloatInArrayBox(number: .nan))
63+
XCTFail("Did not expect to have a result: \(result)")
64+
}
65+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
66+
XCTAssert(value.isNaN) // expected
67+
XCTAssertEqual(context.codingPath.count, 1)
68+
XCTAssertEqual(context.codingPath.first?.stringValue, "Index 0")
69+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.nan directly in JSON.")
70+
}
71+
catch {
72+
XCTFail("Unexpected error: \(error)")
73+
}
74+
}
75+
76+
func testEncodeFloatInf() {
77+
do {
78+
let result = try PureSwiftJSONCoding.JSONEncoder().encode(FloatInArrayBox(number: .infinity))
79+
XCTFail("Did not expect to have a result: \(result)")
80+
}
81+
catch Swift.EncodingError.invalidValue(let value as Float, let context) {
82+
XCTAssert(value.isInfinite) // expected
83+
XCTAssertEqual(context.codingPath.count, 1)
84+
XCTAssertEqual(context.codingPath.first?.stringValue, "Index 0")
85+
XCTAssertEqual(context.debugDescription, "Unable to encode Float.inf directly in JSON.")
86+
}
87+
catch {
88+
// missing expected catch
89+
XCTFail("Unexpected error: \(error)")
90+
}
91+
}
92+
93+
94+
// MARK: - Nested Container -
95+
796
func testNestedKeyedContainer() {
897
struct ObjectInArray: Encodable {
998
let firstName: String

Tests/LearningTests/FoundationJSONEncoderTests.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,61 @@ class FoundationJSONEncoderTests: XCTestCase {
5454
XCTFail("Unexpected error: \(error)")
5555
}
5656
}
57-
57+
5858
func testEncodeTopLevelDoubleInfinity() throws {
5959
do {
6060
_ = try JSONEncoder().encode(Double.infinity)
6161
}
62-
catch Swift.EncodingError.invalidValue(let value as Double, _) {
62+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
63+
print(context)
64+
XCTAssert(value.isInfinite) // expected
65+
}
66+
catch {
67+
XCTFail("Unexpected error: \(error)")
68+
}
69+
}
70+
71+
struct DoubleBox: Encodable {
72+
let number: Double
73+
}
74+
75+
func testEncodeKeyedContainterDoubleInfinity() throws {
76+
do {
77+
_ = try JSONEncoder().encode(DoubleBox(number: Double.infinity))
78+
}
79+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
80+
print(context)
6381
XCTAssert(value.isInfinite) // expected
6482
}
6583
catch {
6684
XCTFail("Unexpected error: \(error)")
6785
}
6886
}
87+
88+
struct DoubleInArrayBox: Encodable {
89+
let number: Double
90+
91+
func encode(to encoder: Encoder) throws {
92+
var container = encoder.unkeyedContainer()
93+
try container.encode(number)
94+
}
95+
}
96+
97+
func testEncodeDoubleInUnkeyedContainerNAN() {
98+
do {
99+
let result = try JSONEncoder().encode(DoubleInArrayBox(number: .nan))
100+
XCTFail("Did not expect to have a result: \(result)")
101+
}
102+
catch Swift.EncodingError.invalidValue(let value as Double, let context) {
103+
XCTAssert(value.isNaN) // expected
104+
XCTAssertEqual(context.codingPath.count, 1)
105+
XCTAssertEqual(context.codingPath.first?.stringValue, "Index 0")
106+
print(context)
107+
}
108+
catch {
109+
XCTFail("Unexpected error: \(error)")
110+
}
111+
}
69112

70113
}
71114

0 commit comments

Comments
 (0)