More sparse field encoder tests

This commit is contained in:
Mathew Polzin
2019-08-06 08:27:22 -07:00
parent e23ec090ed
commit 6ba217f553
2 changed files with 174 additions and 5 deletions
@@ -50,7 +50,8 @@ public struct SparseFieldKeyedEncodingContainer<Key, SparseKey>: KeyedEncodingCo
self.allowedKeys = allowedKeys
}
private func shouldAllow(key: Key) -> Bool {
/// Ask the container whether the given key should be encoded.
public func shouldAllow(key: Key) -> Bool {
if let key = key as? SparseKey {
return allowedKeys.contains(key)
}
@@ -157,6 +158,9 @@ public struct SparseFieldKeyedEncodingContainer<Key, SparseKey>: KeyedEncodingCo
forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
guard shouldAllow(key: key) else {
return KeyedEncodingContainer(
// TODO: not needed by JSONAPI library, but for completeness could
// add an EmptyObjectEncoder that can be returned here so that
// at least nothing gets encoded within the nested container
SparseFieldKeyedEncodingContainer<NestedKey, SparseKey>(wrapping: wrappedContainer.nestedContainer(keyedBy: keyType,
forKey: key),
encoding: [])
@@ -172,7 +176,9 @@ public struct SparseFieldKeyedEncodingContainer<Key, SparseKey>: KeyedEncodingCo
public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
guard shouldAllow(key: key) else {
// TODO: Seems like this might not work as expected... maybe need an empty unkeyed container
// TODO: not needed by JSONAPI library, but for completeness could
// add an EmptyObjectEncoder that can be returned here so that
// at least nothing gets encoded within the nested container
return wrappedContainer.nestedUnkeyedContainer(forKey: key)
}
@@ -180,14 +186,19 @@ public struct SparseFieldKeyedEncodingContainer<Key, SparseKey>: KeyedEncodingCo
}
public mutating func superEncoder() -> Encoder {
return wrappedContainer.superEncoder()
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(),
encoding: allowedKeys)
}
public mutating func superEncoder(forKey key: Key) -> Encoder {
guard shouldAllow(key: key) else {
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key), encoding: [SparseKey]())
// NOTE: We are creating a sparse field encoder with no allowed keys
// here because the given key should not be allowed.
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key),
encoding: [SparseKey]())
}
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key), encoding: allowedKeys)
return SparseFieldEncoder(wrapping: wrappedContainer.superEncoder(forKey: key),
encoding: allowedKeys)
}
}
@@ -85,6 +85,73 @@ class SparseFieldEncoderTests: XCTestCase {
XCTAssertNil(allThingsOnDeserialized["nested"])
XCTAssertEqual(allThingsOnDeserialized.count, 0)
}
func test_NilEncode() {
let encoder = JSONEncoder()
let nilOn = try! encoder.encode(NilWrapper(fields: [.hello]))
let nilOff = try! encoder.encode(NilWrapper(fields: []))
let nilOnDeserialized = try! JSONSerialization.jsonObject(with: nilOn,
options: []) as! [String: Any]
let nilOffDeserialized = try! JSONSerialization.jsonObject(with: nilOff,
options: []) as! [String: Any]
XCTAssertEqual(nilOnDeserialized.count, 1)
XCTAssertNotNil(nilOnDeserialized["hello"] as? NSNull)
XCTAssertEqual(nilOffDeserialized.count, 0)
}
func test_NestedContainers() {
let encoder = JSONEncoder()
let nestedOn = try! encoder.encode(NestedWrapper(fields: [.hello, .world]))
let nestedOff = try! encoder.encode(NestedWrapper(fields: []))
let nestedOnDeserialized = try! JSONSerialization.jsonObject(with: nestedOn,
options: []) as! [String: Any]
let nestedOffDeserialized = try! JSONSerialization.jsonObject(with: nestedOff,
options: []) as! [String: Any]
XCTAssertEqual(nestedOnDeserialized.count, 2)
XCTAssertEqual((nestedOnDeserialized["hello"] as? [String: Bool])?["nestedKey"], true)
XCTAssertEqual((nestedOnDeserialized["world"] as? [Bool])?.first, false)
// NOTE: When a nested container is explicitly requested,
// the best we can do to omit the field is to encode
// nothing _within_ the nested container.
XCTAssertEqual(nestedOffDeserialized.count, 2)
// TODO: The container currently does not encode empty object
// for the keyed nested container but I think it should.
XCTAssertEqual((nestedOffDeserialized["hello"] as? [String: Bool])?.count, 1)
// TODO: The container currently does not encode empty array
// for the unkeyed nested container but I think it should.
XCTAssertEqual((nestedOffDeserialized["world"] as? [Bool])?.count, 1)
}
func test_SuperEncoderIsStillSparse() {
let encoder = JSONEncoder()
let superOn = try! encoder.encode(SuperWrapper(fields: [.hello, .world]))
let superOff = try! encoder.encode(SuperWrapper(fields: []))
let superOnDeserialized = try! JSONSerialization.jsonObject(with: superOn,
options: []) as! [String: Any]
let superOffDeserialized = try! JSONSerialization.jsonObject(with: superOff,
options: []) as! [String: Any]
XCTAssertEqual(superOnDeserialized.count, 2)
XCTAssertEqual((superOnDeserialized["hello"] as? [String: Bool])?["hello"], true)
XCTAssertEqual((superOnDeserialized["super"] as? [String: Bool])?["world"], false)
// NOTE: When explicitly requesting a super encoder
// the best we can do is tell the super encoder only
// to encode the same keys
XCTAssertEqual(superOffDeserialized.count, 2)
XCTAssertEqual((superOffDeserialized["hello"] as? [String: Bool])?.count, 0)
XCTAssertEqual((superOffDeserialized["super"] as? [String: Bool])?.count, 0)
}
}
extension SparseFieldEncoderTests {
@@ -189,4 +256,95 @@ extension SparseFieldEncoderTests {
}
}
}
struct NilWrapper: Encodable {
let fields: [NilWrapped.CodingKeys]
func encode(to encoder: Encoder) throws {
let sparseEncoder = SparseFieldEncoder(wrapping: encoder,
encoding: fields)
try NilWrapped()
.encode(to: sparseEncoder)
}
struct NilWrapped: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeNil(forKey: .hello)
}
enum CodingKeys: String, Equatable, CodingKey {
case hello
}
}
}
struct NestedWrapper: Encodable {
let fields: [NestedWrapped.CodingKeys]
func encode(to encoder: Encoder) throws {
let sparseEncoder = SparseFieldEncoder(wrapping: encoder,
encoding: fields)
try NestedWrapped()
.encode(to: sparseEncoder)
}
struct NestedWrapped: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var nestedContainer1 = container.nestedContainer(keyedBy: NestedKeys.self, forKey: .hello)
var nestedContainer2 = container.nestedUnkeyedContainer(forKey: .world)
try nestedContainer1.encode(true, forKey: .nestedKey)
try nestedContainer2.encode(false)
}
enum CodingKeys: String, Equatable, CodingKey {
case hello
case world
}
enum NestedKeys: String, CodingKey {
case nestedKey
}
}
}
struct SuperWrapper: Encodable {
let fields: [SuperWrapped.CodingKeys]
func encode(to encoder: Encoder) throws {
let sparseEncoder = SparseFieldEncoder(wrapping: encoder,
encoding: fields)
try SuperWrapped()
.encode(to: sparseEncoder)
}
struct SuperWrapped: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let superEncoder1 = container.superEncoder(forKey: .hello)
let superEncoder2 = container.superEncoder()
var container1 = superEncoder1.container(keyedBy: CodingKeys.self)
var container2 = superEncoder2.container(keyedBy: CodingKeys.self)
try container1.encode(true, forKey: .hello)
try container2.encode(false, forKey: .world)
}
enum CodingKeys: String, Equatable, CodingKey {
case hello
case world
}
}
}
}