Skip to content

Commit 8b932ad

Browse files
authored
Add Encodable conformance to JSONValue (#113)
1 parent 31353c2 commit 8b932ad

File tree

2 files changed

+102
-17
lines changed

2 files changed

+102
-17
lines changed

Sources/GoogleAI/JSONValue.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,29 @@ extension JSONValue: Decodable {
6868
}
6969
}
7070

71+
extension JSONValue: Encodable {
72+
public func encode(to encoder: Encoder) throws {
73+
var container = encoder.singleValueContainer()
74+
switch self {
75+
case .null:
76+
try container.encodeNil()
77+
case let .number(numberValue):
78+
// Convert to `Decimal` before encoding for consistent floating-point serialization across
79+
// platforms. E.g., `Double` serializes 3.14159 as 3.1415899999999999 in some cases and
80+
// 3.14159 in others. See
81+
// https://forums.swift.org/t/jsonencoder-encodable-floating-point-rounding-error/41390/4 for
82+
// more details.
83+
try container.encode(Decimal(numberValue))
84+
case let .string(stringValue):
85+
try container.encode(stringValue)
86+
case let .bool(boolValue):
87+
try container.encode(boolValue)
88+
case let .object(objectValue):
89+
try container.encode(objectValue)
90+
case let .array(arrayValue):
91+
try container.encode(arrayValue)
92+
}
93+
}
94+
}
95+
7196
extension JSONValue: Equatable {}

Tests/GoogleAITests/JSONValueTests.swift

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,53 @@ import XCTest
1616
@testable import GoogleGenerativeAI
1717

1818
final class JSONValueTests: XCTestCase {
19+
let decoder = JSONDecoder()
20+
let encoder = JSONEncoder()
21+
22+
let numberKey = "pi"
23+
let numberValue = 3.14159
24+
let numberValueEncoded = "3.14159"
25+
let stringKey = "hello"
26+
let stringValue = "Hello, world!"
27+
28+
override func setUp() {
29+
encoder.outputFormatting = .sortedKeys
30+
}
31+
1932
func testDecodeNull() throws {
2033
let jsonData = try XCTUnwrap("null".data(using: .utf8))
2134

22-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
35+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
2336

2437
XCTAssertEqual(jsonObject, .null)
2538
}
2639

2740
func testDecodeNumber() throws {
28-
let expectedNumber = 3.14159
29-
let jsonData = try XCTUnwrap("\(expectedNumber)".data(using: .utf8))
41+
let jsonData = try XCTUnwrap("\(numberValue)".data(using: .utf8))
3042

31-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
43+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
3244

33-
XCTAssertEqual(jsonObject, .number(expectedNumber))
45+
XCTAssertEqual(jsonObject, .number(numberValue))
3446
}
3547

3648
func testDecodeString() throws {
37-
let expectedString = "hello-world"
38-
let jsonData = try XCTUnwrap("\"\(expectedString)\"".data(using: .utf8))
49+
let jsonData = try XCTUnwrap("\"\(stringValue)\"".data(using: .utf8))
3950

40-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
51+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
4152

42-
XCTAssertEqual(jsonObject, .string(expectedString))
53+
XCTAssertEqual(jsonObject, .string(stringValue))
4354
}
4455

4556
func testDecodeBool() throws {
4657
let expectedBool = true
4758
let jsonData = try XCTUnwrap("\(expectedBool)".data(using: .utf8))
4859

49-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
60+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
5061

5162
XCTAssertEqual(jsonObject, .bool(expectedBool))
5263
}
5364

5465
func testDecodeObject() throws {
55-
let numberKey = "pi"
56-
let numberValue = 3.14159
57-
let stringKey = "hello"
58-
let stringValue = "world"
5966
let expectedObject: JSONObject = [
6067
numberKey: .number(numberValue),
6168
stringKey: .string(stringValue),
@@ -68,18 +75,71 @@ final class JSONValueTests: XCTestCase {
6875
"""
6976
let jsonData = try XCTUnwrap(json.data(using: .utf8))
7077

71-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
78+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
7279

7380
XCTAssertEqual(jsonObject, .object(expectedObject))
7481
}
7582

7683
func testDecodeArray() throws {
77-
let numberValue = 3.14159
7884
let expectedArray: [JSONValue] = [.null, .number(numberValue)]
7985
let jsonData = try XCTUnwrap("[ null, \(numberValue) ]".data(using: .utf8))
8086

81-
let jsonObject = try XCTUnwrap(JSONDecoder().decode(JSONValue.self, from: jsonData))
87+
let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
8288

8389
XCTAssertEqual(jsonObject, .array(expectedArray))
8490
}
91+
92+
func testEncodeNull() throws {
93+
let jsonData = try encoder.encode(JSONValue.null)
94+
95+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
96+
XCTAssertEqual(json, "null")
97+
}
98+
99+
func testEncodeNumber() throws {
100+
let jsonData = try encoder.encode(JSONValue.number(numberValue))
101+
102+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
103+
XCTAssertEqual(json, "\(numberValue)")
104+
}
105+
106+
func testEncodeString() throws {
107+
let jsonData = try encoder.encode(JSONValue.string(stringValue))
108+
109+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
110+
XCTAssertEqual(json, "\"\(stringValue)\"")
111+
}
112+
113+
func testEncodeBool() throws {
114+
let boolValue = true
115+
116+
let jsonData = try encoder.encode(JSONValue.bool(boolValue))
117+
118+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
119+
XCTAssertEqual(json, "\(boolValue)")
120+
}
121+
122+
func testEncodeObject() throws {
123+
let objectValue: JSONObject = [
124+
numberKey: .number(numberValue),
125+
stringKey: .string(stringValue),
126+
]
127+
128+
let jsonData = try encoder.encode(JSONValue.object(objectValue))
129+
130+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
131+
XCTAssertEqual(
132+
json,
133+
"{\"\(stringKey)\":\"\(stringValue)\",\"\(numberKey)\":\(numberValueEncoded)}"
134+
)
135+
}
136+
137+
func testEncodeArray() throws {
138+
let arrayValue: [JSONValue] = [.null, .number(numberValue)]
139+
140+
let jsonData = try encoder.encode(JSONValue.array(arrayValue))
141+
142+
let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
143+
XCTAssertEqual(json, "[null,\(numberValueEncoded)]")
144+
}
85145
}

0 commit comments

Comments
 (0)