Skip to content

Commit 3cf2496

Browse files
Support custom JSON coders (vapor#127)
* Support non Foundation JSON coders * add a `PostgresJSONEncoder` protocol with an API that mirrors the Foundation `JSONEncoder`'s decoding function * add global `_defaultJSONEncoder` variable used in the JSON and JSONB type and that is defaulted to a Foundation `JSONEncoder` * add a `PostgresJSONDecoder` protocol with an API that mirrors the Foundation `JSONDecoder`'s decoding function * add global `_defaultJSONDecoder` variable used in the JSON and JSONB type and that is defaulted to a Foundation `JSONDecoder` * Add docblocks for _defaultJSON{encoder,decoder} and their respective unit tests
1 parent dddb196 commit 3cf2496

File tree

5 files changed

+92
-4
lines changed

5 files changed

+92
-4
lines changed

Diff for: Sources/PostgresNIO/Data/PostgresData+JSON.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extension PostgresData {
1111
}
1212

1313
public init<T>(json value: T) throws where T: Encodable {
14-
let jsonData = try JSONEncoder().encode(value)
14+
let jsonData = try PostgresNIO._defaultJSONEncoder.encode(value)
1515
self.init(json: jsonData)
1616
}
1717

@@ -32,7 +32,7 @@ extension PostgresData {
3232
guard let data = self.json else {
3333
return nil
3434
}
35-
return try JSONDecoder().decode(T.self, from: data)
35+
return try PostgresNIO._defaultJSONDecoder.decode(T.self, from: data)
3636
}
3737
}
3838

Diff for: Sources/PostgresNIO/Data/PostgresData+JSONB.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension PostgresData {
1515
}
1616

1717
public init<T>(jsonb value: T) throws where T: Encodable {
18-
let jsonData = try JSONEncoder().encode(value)
18+
let jsonData = try PostgresNIO._defaultJSONEncoder.encode(value)
1919
self.init(jsonb: jsonData)
2020
}
2121

@@ -43,7 +43,7 @@ extension PostgresData {
4343
return nil
4444
}
4545

46-
return try JSONDecoder().decode(T.self, from: data)
46+
return try PostgresNIO._defaultJSONDecoder.decode(T.self, from: data)
4747
}
4848
}
4949

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Foundation
2+
3+
/// A protocol that mimmicks the Foundation `JSONDecoder.decode(_:from:)` function.
4+
/// Conform a non-Foundation JSON decoder to this protocol if you want PostgresNIO to be
5+
/// able to use it when decoding JSON & JSONB values (see `PostgresNIO._defaultJSONDecoder`)
6+
public protocol PostgresJSONDecoder {
7+
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
8+
}
9+
10+
extension JSONDecoder: PostgresJSONDecoder {}
11+
12+
/// The default JSON decoder used by PostgresNIO when decoding JSON & JSONB values.
13+
/// As `_defaultJSONDecoder` will be reused for decoding all JSON & JSONB values
14+
/// from potentially multiple threads at once, you must ensure your custom JSON decoder is
15+
/// thread safe internally like `Foundation.JSONDecoder`.
16+
public var _defaultJSONDecoder: PostgresJSONDecoder = JSONDecoder()
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Foundation
2+
3+
/// A protocol that mimmicks the Foundation `JSONEncoder.encode(_:)` function.
4+
/// Conform a non-Foundation JSON encoder to this protocol if you want PostgresNIO to be
5+
/// able to use it when encoding JSON & JSONB values (see `PostgresNIO._defaultJSONEncoder`)
6+
public protocol PostgresJSONEncoder {
7+
func encode<T>(_ value: T) throws -> Data where T : Encodable
8+
}
9+
10+
extension JSONEncoder: PostgresJSONEncoder {}
11+
12+
/// The default JSON encoder used by PostgresNIO when encoding JSON & JSONB values.
13+
/// As `_defaultJSONEncoder` will be reused for encoding all JSON & JSONB values
14+
/// from potentially multiple threads at once, you must ensure your custom JSON encoder is
15+
/// thread safe internally like `Foundation.JSONEncoder`.
16+
public var _defaultJSONEncoder: PostgresJSONEncoder = JSONEncoder()

Diff for: Tests/PostgresNIOTests/PostgresNIOTests.swift

+56
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,62 @@ final class PostgresNIOTests: XCTestCase {
10041004
XCTAssertEqual(rows[0].column("min64")?.int64, .min)
10051005
XCTAssertEqual(rows[0].column("max64")?.int64, .max)
10061006
}
1007+
1008+
// https://github.com/vapor/postgres-nio/issues/126
1009+
func testCustomJSONEncoder() throws {
1010+
let previousDefaultJSONEncoder = PostgresNIO._defaultJSONEncoder
1011+
defer {
1012+
PostgresNIO._defaultJSONEncoder = previousDefaultJSONEncoder
1013+
}
1014+
final class CustomJSONEncoder: PostgresJSONEncoder {
1015+
var didEncode = false
1016+
func encode<T>(_ value: T) throws -> Data where T : Encodable {
1017+
self.didEncode = true
1018+
return try JSONEncoder().encode(value)
1019+
}
1020+
}
1021+
struct Object: Codable {
1022+
var foo: Int
1023+
var bar: Int
1024+
}
1025+
let customJSONEncoder = CustomJSONEncoder()
1026+
PostgresNIO._defaultJSONEncoder = customJSONEncoder
1027+
let _ = try PostgresData(json: Object(foo: 1, bar: 2))
1028+
XCTAssert(customJSONEncoder.didEncode)
1029+
1030+
let customJSONBEncoder = CustomJSONEncoder()
1031+
PostgresNIO._defaultJSONEncoder = customJSONBEncoder
1032+
let _ = try PostgresData(jsonb: Object(foo: 1, bar: 2))
1033+
XCTAssert(customJSONBEncoder.didEncode)
1034+
}
1035+
1036+
// https://github.com/vapor/postgres-nio/issues/126
1037+
func testCustomJSONDecoder() throws {
1038+
let previousDefaultJSONDecoder = PostgresNIO._defaultJSONDecoder
1039+
defer {
1040+
PostgresNIO._defaultJSONDecoder = previousDefaultJSONDecoder
1041+
}
1042+
final class CustomJSONDecoder: PostgresJSONDecoder {
1043+
var didDecode = false
1044+
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
1045+
self.didDecode = true
1046+
return try JSONDecoder().decode(type, from: data)
1047+
}
1048+
}
1049+
struct Object: Codable {
1050+
var foo: Int
1051+
var bar: Int
1052+
}
1053+
let customJSONDecoder = CustomJSONDecoder()
1054+
PostgresNIO._defaultJSONDecoder = customJSONDecoder
1055+
let _ = try PostgresData(json: Object(foo: 1, bar: 2)).json(as: Object.self)
1056+
XCTAssert(customJSONDecoder.didDecode)
1057+
1058+
let customJSONBDecoder = CustomJSONDecoder()
1059+
PostgresNIO._defaultJSONDecoder = customJSONBDecoder
1060+
let _ = try PostgresData(json: Object(foo: 1, bar: 2)).json(as: Object.self)
1061+
XCTAssert(customJSONBDecoder.didDecode)
1062+
}
10071063
}
10081064

10091065
func env(_ name: String) -> String? {

0 commit comments

Comments
 (0)