Skip to content

Commit cc58455

Browse files
authored
Merge pull request #12 from swhitty/KeyEncodingDecodingStrategies
snake_case keys via Encoding/Decoding strategies
2 parents 900eaff + 2bc5f43 commit cc58455

File tree

5 files changed

+210
-35
lines changed

5 files changed

+210
-35
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,27 @@ decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
132132
let values = try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity])
133133
```
134134

135+
## Key Strategy
136+
137+
Keys can be encoded to snake_case by setting the strategy:
138+
139+
```swift
140+
var encoder = KeyValueEncoder()
141+
encoder.keyEncodingStrategy = .convertToSnakeCase
142+
143+
// ["first_name": "fish", "surname": "chips"]
144+
let dict = try encoder.encode(Person(firstName: "fish", surname: "chips))
145+
```
146+
147+
And decoded from snake_case:
148+
149+
```swift
150+
var decoder = KeyValueDecoder()
151+
decoder.keyDecodingStrategy = .convertFromSnakeCase
152+
153+
let person = try decoder.decode(Person.self, from: dict)
154+
```
155+
135156
## UserDefaults
136157
Encode and decode [`Codable`](https://developer.apple.com/documentation/swift/codable) types with [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults):
137158

Sources/KeyValueDecoder.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public struct KeyValueDecoder: Sendable {
4444
/// The strategy to use for decoding BinaryInteger types. Defaults to `.exact` for lossless conversion between types.
4545
public var intDecodingStrategy: IntDecodingStrategy = .exact
4646

47+
/// The strategy to use for decoding each types keys.
48+
public var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
49+
4750
/// Initializes `self` with default strategy.
4851
public init () {
4952
self.userInfo = [:]
@@ -82,6 +85,15 @@ public struct KeyValueDecoder: Sendable {
8285
/// Floating point conversions are also clamped, rounded when a rule is provided
8386
case clamping(roundingRule: FloatingPointRoundingRule?)
8487
}
88+
89+
/// Strategy to determine how to decode a type’s coding keys from String values.
90+
public enum KeyDecodingStrategy: Sendable {
91+
/// A key decoding strategy that converts snake-case keys to camel-case keys.
92+
case convertFromSnakeCase
93+
94+
/// A key encoding strategy that doesn’t change key names during encoding.
95+
case useDefaultKeys
96+
}
8597
}
8698

8799
#if canImport(Combine)
@@ -105,12 +117,14 @@ private extension KeyValueDecoder {
105117
struct DecodingStrategy {
106118
var optionals: NilDecodingStrategy
107119
var integers: IntDecodingStrategy
120+
var keys: KeyDecodingStrategy
108121
}
109122

110123
var strategy: DecodingStrategy {
111124
DecodingStrategy(
112125
optionals: nilDecodingStrategy,
113-
integers: intDecodingStrategy
126+
integers: intDecodingStrategy,
127+
keys: keyDecodingStrategy
114128
)
115129
}
116130

@@ -381,7 +395,8 @@ private extension KeyValueDecoder {
381395

382396
func container(for key: Key) throws -> SingleContainer {
383397
let path = codingPath.appending(key: key)
384-
guard let value = storage[key.stringValue] else {
398+
let kkk = strategy.keys.makeStorageKey(for: key.stringValue)
399+
guard let value = storage[kkk] else {
385400
let keyPath = codingPath.makeKeyPath(appending: key)
386401
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Dictionary does not contain key \(keyPath)")
387402
throw DecodingError.keyNotFound(key, context)
@@ -395,7 +410,7 @@ private extension KeyValueDecoder {
395410
}
396411

397412
func contains(_ key: Key) -> Bool {
398-
return storage[key.stringValue] != nil
413+
return storage[strategy.keys.makeStorageKey(for: key.stringValue)] != nil
399414
}
400415

401416
func decodeNil(forKey key: Key) throws -> Bool {
@@ -640,6 +655,16 @@ private extension KeyValueDecoder {
640655
}
641656
}
642657

658+
extension KeyValueDecoder.KeyDecodingStrategy {
659+
660+
func makeStorageKey(for key: String) -> String {
661+
switch self {
662+
case .useDefaultKeys: return key
663+
case .convertFromSnakeCase: return key.toSnakeCase()
664+
}
665+
}
666+
}
667+
643668
extension BinaryInteger {
644669

645670
init?(from source: Double, using strategy: KeyValueDecoder.IntDecodingStrategy) {

0 commit comments

Comments
 (0)