Skip to content

Commit 090df4e

Browse files
committed
NilEncodingStrategy
1 parent 345cfee commit 090df4e

File tree

6 files changed

+442
-79
lines changed

6 files changed

+442
-79
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,47 @@ let meals = try KeyValuDecoder().decode([String].self, from: ["fish", 1])
5555
let user = try KeyValuDecoder().decode(User.self, from: [["id": 1, "name": "Herbert"], ["id:" 2])
5656
```
5757

58+
## Nil Encoding/Decoding Strategy
59+
60+
The encoding of `Optional.none` can be adjusted by setting the strategy.
61+
62+
The default strategy preserves `Optional.none`:
63+
64+
```swift
65+
let encoder = KeyValueEncoder()
66+
encoder.strategy = .default
67+
68+
// [1, 2, nil, 3]
69+
let any = try encoder.encode([1, 2, Int?.none, 3])
70+
```
71+
72+
Compatibility with [`PropertyListEncoder`](https://developer.apple.com/documentation/foundation/propertylistencoder) is preserved using a placeholder string:
73+
74+
```swift
75+
encoder.strategy = .stringNull
76+
77+
// [1, 2, "$null", 3]
78+
let any = try encoder.encode([1, 2, Int?.none, 3])
79+
```
80+
81+
Compatibility with [`JSONSerialization`](https://developer.apple.com/documentation/foundation/jsonserialization) is preserved using [`NSNull`](https://developer.apple.com/documentation/foundation/nsnull):
82+
83+
```swift
84+
encoder.strategy = .nsNull
85+
86+
// [1, 2, NSNull(), 3]
87+
let any = try encoder.encode([1, 2, Int?.none, 3])
88+
```
89+
90+
Nil values can also be completely removed:
91+
92+
```swift
93+
encoder.strategy = .removed
94+
95+
// [1, 2, 3]
96+
let any = try encoder.encode([1, 2, Int?.none, 3])
97+
```
98+
5899
## UserDefaults
59100
Encode and decode [`Codable`](https://developer.apple.com/documentation/swift/codable) types with [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults):
60101

Sources/KeyValueDecoder.swift

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,26 @@ import CoreFoundation
3535
public final class KeyValueDecoder {
3636

3737
public var userInfo: [CodingUserInfoKey: Any]
38+
public var nilDecodingStrategy: NilDecodingStrategy = .default
3839

3940
public init () {
4041
self.userInfo = [:]
4142
}
4243

4344
public func decode<T: Decodable>(_ type: T.Type, from value: Any) throws -> T {
44-
let container = SingleContainer(value: value, codingPath: [], userInfo: userInfo)
45+
let container = SingleContainer(value: value, codingPath: [], userInfo: userInfo, nilDecodingStrategy: nilDecodingStrategy)
4546
return try container.decode(type)
4647
}
48+
49+
public typealias NilDecodingStrategy = KeyValueEncoder.NilEncodingStrategy
4750
}
4851

4952
extension KeyValueDecoder {
5053

51-
// To assist in representing Optional<Any> because `Any` always casts to Optional<Any>
52-
static func isValueNil(_ value: Any) -> Bool {
53-
guard Mirror(reflecting: value).displayStyle == .optional else {
54-
return value is NSNull
55-
}
56-
57-
if case Optional<Any>.some = value {
58-
return false
59-
} else {
60-
return true
61-
}
54+
static func makePlistCompatible() -> KeyValueDecoder {
55+
let decoder = KeyValueDecoder()
56+
decoder.nilDecodingStrategy = .stringNull
57+
return decoder
6258
}
6359
}
6460

@@ -80,14 +76,15 @@ private extension KeyValueDecoder {
8076
let keyed = try KeyedContainer<Key>(
8177
codingPath: codingPath,
8278
storage: container.decode([String: Any].self),
83-
userInfo: userInfo
79+
userInfo: userInfo,
80+
nilDecodingStrategy: container.nilDecodingStrategy
8481
)
8582
return KeyedDecodingContainer(keyed)
8683
}
8784

8885
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
8986
let storage = try container.decode([Any].self)
90-
return UnkeyedContainer(codingPath: codingPath, storage: storage, userInfo: userInfo)
87+
return UnkeyedContainer(codingPath: codingPath, storage: storage, userInfo: userInfo, nilDecodingStrategy: container.nilDecodingStrategy)
9188
}
9289

9390
func singleValueContainer() throws -> SingleValueDecodingContainer {
@@ -99,25 +96,23 @@ private extension KeyValueDecoder {
9996

10097
let codingPath: [CodingKey]
10198
let userInfo: [CodingUserInfoKey: Any]
99+
let nilDecodingStrategy: NilDecodingStrategy
102100

103101
private var value: Any
104102

105-
init(value: Any, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any]) {
103+
init(value: Any, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any], nilDecodingStrategy: NilDecodingStrategy) {
106104
self.value = value
107105
self.codingPath = codingPath
108106
self.userInfo = userInfo
107+
self.nilDecodingStrategy = nilDecodingStrategy
109108
}
110109

111110
func decodeNil() -> Bool {
112-
KeyValueDecoder.isValueNil(value)
111+
nilDecodingStrategy.isNull(value)
113112
}
114113

115114
private var valueDescription: String {
116-
if value is NSNull {
117-
return "NSNull"
118-
} else {
119-
return decodeNil() ? "nil" : String(describing: type(of: value))
120-
}
115+
nilDecodingStrategy.isNull(value) ? "nil" : String(describing: type(of: value))
121116
}
122117

123118
func getValue<T>(of type: T.Type = T.self) throws -> T {
@@ -289,11 +284,13 @@ private extension KeyValueDecoder {
289284
let storage: [String: Any]
290285
let codingPath: [CodingKey]
291286
private let userInfo: [CodingUserInfoKey: Any]
287+
private let nilDecodingStrategy: NilDecodingStrategy
292288

293-
init(codingPath: [CodingKey], storage: [String: Any], userInfo: [CodingUserInfoKey: Any]) {
289+
init(codingPath: [CodingKey], storage: [String: Any], userInfo: [CodingUserInfoKey: Any], nilDecodingStrategy: NilDecodingStrategy) {
294290
self.codingPath = codingPath
295291
self.storage = storage
296292
self.userInfo = userInfo
293+
self.nilDecodingStrategy = nilDecodingStrategy
297294
}
298295

299296
var allKeys: [Key] {
@@ -313,7 +310,7 @@ private extension KeyValueDecoder {
313310
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Dictionary does not contain key \(keyPath)")
314311
throw DecodingError.keyNotFound(key, context)
315312
}
316-
return SingleContainer(value: value, codingPath: path, userInfo: userInfo)
313+
return SingleContainer(value: value, codingPath: path, userInfo: userInfo, nilDecodingStrategy: nilDecodingStrategy)
317314
}
318315

319316
func contains(_ key: Key) -> Bool {
@@ -389,7 +386,8 @@ private extension KeyValueDecoder {
389386
let keyed = try KeyedContainer<NestedKey>(
390387
codingPath: container.codingPath,
391388
storage: container.decode([String: Any].self),
392-
userInfo: userInfo
389+
userInfo: userInfo,
390+
nilDecodingStrategy: nilDecodingStrategy
393391
)
394392
return KeyedDecodingContainer<NestedKey>(keyed)
395393
}
@@ -399,12 +397,13 @@ private extension KeyValueDecoder {
399397
return try UnkeyedContainer(
400398
codingPath: container.codingPath,
401399
storage: container.decode([Any].self),
402-
userInfo: userInfo
400+
userInfo: userInfo,
401+
nilDecodingStrategy: nilDecodingStrategy
403402
)
404403
}
405404

406405
func superDecoder() throws -> Swift.Decoder {
407-
let container = SingleContainer(value: storage, codingPath: codingPath, userInfo: userInfo)
406+
let container = SingleContainer(value: storage, codingPath: codingPath, userInfo: userInfo, nilDecodingStrategy: nilDecodingStrategy)
408407
return Decoder(container: container)
409408
}
410409

@@ -419,11 +418,13 @@ private extension KeyValueDecoder {
419418

420419
let storage: [Any]
421420
private let userInfo: [CodingUserInfoKey: Any]
421+
private let nilDecodingStrategy: NilDecodingStrategy
422422

423-
init(codingPath: [CodingKey], storage: [Any], userInfo: [CodingUserInfoKey: Any]) {
423+
init(codingPath: [CodingKey], storage: [Any], userInfo: [CodingUserInfoKey: Any], nilDecodingStrategy: NilDecodingStrategy) {
424424
self.codingPath = codingPath
425425
self.storage = storage
426426
self.userInfo = userInfo
427+
self.nilDecodingStrategy = nilDecodingStrategy
427428
self.currentIndex = storage.startIndex
428429
}
429430

@@ -444,7 +445,7 @@ private extension KeyValueDecoder {
444445
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Array does not contain index \(keyPath)")
445446
throw DecodingError.keyNotFound(AnyCodingKey(intValue: currentIndex), context)
446447
}
447-
return SingleContainer(value: storage[currentIndex], codingPath: path, userInfo: userInfo)
448+
return SingleContainer(value: storage[currentIndex], codingPath: path, userInfo: userInfo, nilDecodingStrategy: nilDecodingStrategy)
448449
}
449450

450451
mutating func decodeNext<T: Decodable>(of type: T.Type = T.self) throws -> T {
@@ -532,7 +533,7 @@ private extension KeyValueDecoder {
532533
}
533534

534535
mutating func superDecoder() -> Swift.Decoder {
535-
let container = SingleContainer(value: storage, codingPath: codingPath, userInfo: userInfo)
536+
let container = SingleContainer(value: storage, codingPath: codingPath, userInfo: userInfo, nilDecodingStrategy: nilDecodingStrategy)
536537
return Decoder(container: container)
537538
}
538539
}

0 commit comments

Comments
 (0)