Skip to content

Commit

Permalink
Support custom JSON coders (#127)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jordanebelanger authored Sep 9, 2020
1 parent dddb196 commit 3cf2496
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Sources/PostgresNIO/Data/PostgresData+JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extension PostgresData {
}

public init<T>(json value: T) throws where T: Encodable {
let jsonData = try JSONEncoder().encode(value)
let jsonData = try PostgresNIO._defaultJSONEncoder.encode(value)
self.init(json: jsonData)
}

Expand All @@ -32,7 +32,7 @@ extension PostgresData {
guard let data = self.json else {
return nil
}
return try JSONDecoder().decode(T.self, from: data)
return try PostgresNIO._defaultJSONDecoder.decode(T.self, from: data)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/PostgresNIO/Data/PostgresData+JSONB.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension PostgresData {
}

public init<T>(jsonb value: T) throws where T: Encodable {
let jsonData = try JSONEncoder().encode(value)
let jsonData = try PostgresNIO._defaultJSONEncoder.encode(value)
self.init(jsonb: jsonData)
}

Expand Down Expand Up @@ -43,7 +43,7 @@ extension PostgresData {
return nil
}

return try JSONDecoder().decode(T.self, from: data)
return try PostgresNIO._defaultJSONDecoder.decode(T.self, from: data)
}
}

Expand Down
16 changes: 16 additions & 0 deletions Sources/PostgresNIO/Utilities/PostgresJSONDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

/// A protocol that mimmicks the Foundation `JSONDecoder.decode(_:from:)` function.
/// Conform a non-Foundation JSON decoder to this protocol if you want PostgresNIO to be
/// able to use it when decoding JSON & JSONB values (see `PostgresNIO._defaultJSONDecoder`)
public protocol PostgresJSONDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}

extension JSONDecoder: PostgresJSONDecoder {}

/// The default JSON decoder used by PostgresNIO when decoding JSON & JSONB values.
/// As `_defaultJSONDecoder` will be reused for decoding all JSON & JSONB values
/// from potentially multiple threads at once, you must ensure your custom JSON decoder is
/// thread safe internally like `Foundation.JSONDecoder`.
public var _defaultJSONDecoder: PostgresJSONDecoder = JSONDecoder()
16 changes: 16 additions & 0 deletions Sources/PostgresNIO/Utilities/PostgresJSONEncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

/// A protocol that mimmicks the Foundation `JSONEncoder.encode(_:)` function.
/// Conform a non-Foundation JSON encoder to this protocol if you want PostgresNIO to be
/// able to use it when encoding JSON & JSONB values (see `PostgresNIO._defaultJSONEncoder`)
public protocol PostgresJSONEncoder {
func encode<T>(_ value: T) throws -> Data where T : Encodable
}

extension JSONEncoder: PostgresJSONEncoder {}

/// The default JSON encoder used by PostgresNIO when encoding JSON & JSONB values.
/// As `_defaultJSONEncoder` will be reused for encoding all JSON & JSONB values
/// from potentially multiple threads at once, you must ensure your custom JSON encoder is
/// thread safe internally like `Foundation.JSONEncoder`.
public var _defaultJSONEncoder: PostgresJSONEncoder = JSONEncoder()
56 changes: 56 additions & 0 deletions Tests/PostgresNIOTests/PostgresNIOTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,62 @@ final class PostgresNIOTests: XCTestCase {
XCTAssertEqual(rows[0].column("min64")?.int64, .min)
XCTAssertEqual(rows[0].column("max64")?.int64, .max)
}

// https://github.com/vapor/postgres-nio/issues/126
func testCustomJSONEncoder() throws {
let previousDefaultJSONEncoder = PostgresNIO._defaultJSONEncoder
defer {
PostgresNIO._defaultJSONEncoder = previousDefaultJSONEncoder
}
final class CustomJSONEncoder: PostgresJSONEncoder {
var didEncode = false
func encode<T>(_ value: T) throws -> Data where T : Encodable {
self.didEncode = true
return try JSONEncoder().encode(value)
}
}
struct Object: Codable {
var foo: Int
var bar: Int
}
let customJSONEncoder = CustomJSONEncoder()
PostgresNIO._defaultJSONEncoder = customJSONEncoder
let _ = try PostgresData(json: Object(foo: 1, bar: 2))
XCTAssert(customJSONEncoder.didEncode)

let customJSONBEncoder = CustomJSONEncoder()
PostgresNIO._defaultJSONEncoder = customJSONBEncoder
let _ = try PostgresData(jsonb: Object(foo: 1, bar: 2))
XCTAssert(customJSONBEncoder.didEncode)
}

// https://github.com/vapor/postgres-nio/issues/126
func testCustomJSONDecoder() throws {
let previousDefaultJSONDecoder = PostgresNIO._defaultJSONDecoder
defer {
PostgresNIO._defaultJSONDecoder = previousDefaultJSONDecoder
}
final class CustomJSONDecoder: PostgresJSONDecoder {
var didDecode = false
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
self.didDecode = true
return try JSONDecoder().decode(type, from: data)
}
}
struct Object: Codable {
var foo: Int
var bar: Int
}
let customJSONDecoder = CustomJSONDecoder()
PostgresNIO._defaultJSONDecoder = customJSONDecoder
let _ = try PostgresData(json: Object(foo: 1, bar: 2)).json(as: Object.self)
XCTAssert(customJSONDecoder.didDecode)

let customJSONBDecoder = CustomJSONDecoder()
PostgresNIO._defaultJSONDecoder = customJSONBDecoder
let _ = try PostgresData(json: Object(foo: 1, bar: 2)).json(as: Object.self)
XCTAssert(customJSONBDecoder.didDecode)
}
}

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

0 comments on commit 3cf2496

Please sign in to comment.