@@ -40,6 +40,9 @@ public struct KeyValueEncoder: Sendable {
4040 /// The strategy to use for encoding `nil`. Defaults to `Optional<Any>.none` which can be cast to any optional type.
4141 public var nilEncodingStrategy : NilEncodingStrategy = . default
4242
43+ /// The strategy to use for encoding each types keys.
44+ public var keyEncodingStrategy : KeyEncodingStrategy = . useDefaultKeys
45+
4346 /// Initializes `self` with default strategies.
4447 public init ( ) {
4548 self . userInfo = [ : ]
@@ -55,6 +58,15 @@ public struct KeyValueEncoder: Sendable {
5558
5659 /// Strategy used to encode nil values.
5760 public typealias NilEncodingStrategy = NilCodingStrategy
61+
62+ /// Strategy to determine how to encode a type’s coding keys as String values.
63+ public enum KeyEncodingStrategy : Sendable {
64+ /// A key encoding strategy that converts camel-case keys to snake-case keys.
65+ case convertToSnakeCase
66+
67+ /// A key encoding strategy that doesn’t change key names during encoding.
68+ case useDefaultKeys
69+ }
5870}
5971
6072/// Strategy used to encode and decode nil values.
@@ -108,7 +120,7 @@ extension KeyValueEncoder {
108120 }
109121
110122 func encodeValue< T: Encodable > ( _ value: T ) throws -> EncodedValue {
111- try Encoder ( userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy ) . encodeToValue ( value)
123+ return try Encoder ( userInfo: userInfo, strategy : strategy ) . encodeToValue ( value)
112124 }
113125}
114126
@@ -149,16 +161,28 @@ private extension KeyValueEncoder.NilEncodingStrategy {
149161
150162private extension KeyValueEncoder {
151163
164+ struct EncodingStrategy {
165+ var optionals : NilEncodingStrategy
166+ var keys : KeyEncodingStrategy
167+ }
168+
169+ var strategy : EncodingStrategy {
170+ EncodingStrategy (
171+ optionals: nilEncodingStrategy,
172+ keys: keyEncodingStrategy
173+ )
174+ }
175+
152176 final class Encoder : Swift . Encoder {
153177
154178 let codingPath : [ any CodingKey ]
155179 let userInfo : [ CodingUserInfoKey : Any ]
156- let nilEncodingStrategy : NilEncodingStrategy
180+ let strategy : EncodingStrategy
157181
158- init ( codingPath: [ any CodingKey ] = [ ] , userInfo: [ CodingUserInfoKey : Any ] , nilEncodingStrategy : NilEncodingStrategy ) {
182+ init ( codingPath: [ any CodingKey ] = [ ] , userInfo: [ CodingUserInfoKey : Any ] , strategy : EncodingStrategy ) {
159183 self . codingPath = codingPath
160184 self . userInfo = userInfo
161- self . nilEncodingStrategy = nilEncodingStrategy
185+ self . strategy = strategy
162186 }
163187
164188 private( set) var container : EncodedValue ? {
@@ -175,19 +199,19 @@ private extension KeyValueEncoder {
175199 }
176200
177201 func container< Key> ( keyedBy type: Key . Type ) -> KeyedEncodingContainer < Key > where Key: CodingKey {
178- let keyed = KeyedContainer < Key > ( codingPath: codingPath, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
202+ let keyed = KeyedContainer < Key > ( codingPath: codingPath, userInfo: userInfo, strategy : strategy )
179203 container = . provider( keyed. getEncodedValue)
180204 return KeyedEncodingContainer ( keyed)
181205 }
182206
183207 func unkeyedContainer( ) -> any UnkeyedEncodingContainer {
184- let unkeyed = UnkeyedContainer ( codingPath: codingPath, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
208+ let unkeyed = UnkeyedContainer ( codingPath: codingPath, userInfo: userInfo, strategy : strategy )
185209 container = . provider( unkeyed. getEncodedValue)
186210 return unkeyed
187211 }
188212
189213 func singleValueContainer( ) -> any SingleValueEncodingContainer {
190- let single = SingleContainer ( codingPath: codingPath, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
214+ let single = SingleContainer ( codingPath: codingPath, userInfo: userInfo, strategy : strategy )
191215 container = . provider( single. getEncodedValue)
192216 return single
193217 }
@@ -209,27 +233,27 @@ private extension KeyValueEncoder {
209233
210234 let codingPath : [ any CodingKey ]
211235 private let userInfo : [ CodingUserInfoKey : Any ]
212- private let nilEncodingStrategy : NilEncodingStrategy
236+ private let strategy : EncodingStrategy
213237
214- init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , nilEncodingStrategy : NilEncodingStrategy ) {
238+ init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , strategy : EncodingStrategy ) {
215239 self . codingPath = codingPath
216240 self . storage = [ : ]
217241 self . userInfo = userInfo
218- self . nilEncodingStrategy = nilEncodingStrategy
242+ self . strategy = strategy
219243 }
220244
221245 private var storage : [ String : EncodedValue ]
222246
223247 func setValue( _ value: Any , forKey key: Key ) {
224- storage [ key. stringValue] = . value( value)
248+ storage [ strategy . keys . makeStorageKey ( for : key. stringValue) ] = . value( value)
225249 }
226250
227251 func setValue( _ value: EncodedValue , forKey key: Key ) {
228- storage [ key. stringValue] = value
252+ storage [ strategy . keys . makeStorageKey ( for : key. stringValue) ] = value
229253 }
230254
231255 func getEncodedValue( ) throws -> EncodedValue {
232- try . value( storage. compactMapValues { try $0. getValue ( strategy: nilEncodingStrategy ) } )
256+ try . value( storage. compactMapValues { try $0. getValue ( strategy: strategy . optionals ) } )
233257 }
234258
235259 func encodeNil( forKey key: Key ) {
@@ -298,8 +322,8 @@ private extension KeyValueEncoder {
298322 return
299323 }
300324
301- let encoder = Encoder ( codingPath: codingPath. appending ( key: key) , userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
302- if let value = try encoder. encodeToValue ( value) . getValue ( strategy: nilEncodingStrategy ) {
325+ let encoder = Encoder ( codingPath: codingPath. appending ( key: key) , userInfo: userInfo, strategy : strategy )
326+ if let value = try encoder. encodeToValue ( value) . getValue ( strategy: strategy . optionals ) {
303327 setValue ( value, forKey: key)
304328 } else {
305329 setValue ( . null, forKey: key)
@@ -308,14 +332,14 @@ private extension KeyValueEncoder {
308332
309333 func nestedContainer< NestedKey> ( keyedBy keyType: NestedKey . Type , forKey key: Key ) -> KeyedEncodingContainer < NestedKey > {
310334 let path = codingPath. appending ( key: key)
311- let keyed = KeyedContainer < NestedKey > ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
335+ let keyed = KeyedContainer < NestedKey > ( codingPath: path, userInfo: userInfo, strategy : strategy )
312336 storage [ key. stringValue] = . provider( keyed. getEncodedValue)
313337 return KeyedEncodingContainer ( keyed)
314338 }
315339
316340 func nestedUnkeyedContainer( forKey key: K ) -> any UnkeyedEncodingContainer {
317341 let path = codingPath. appending ( key: key)
318- let unkeyed = UnkeyedContainer ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
342+ let unkeyed = UnkeyedContainer ( codingPath: path, userInfo: userInfo, strategy : strategy )
319343 storage [ key. stringValue] = . provider( unkeyed. getEncodedValue)
320344 return unkeyed
321345 }
@@ -326,7 +350,7 @@ private extension KeyValueEncoder {
326350
327351 func superEncoder( forKey key: Key ) -> any Swift . Encoder {
328352 let path = codingPath. appending ( key: key)
329- let encoder = Encoder ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
353+ let encoder = Encoder ( codingPath: path, userInfo: userInfo, strategy : strategy )
330354 storage [ key. stringValue] = . provider( encoder. getEncodedValue)
331355 return encoder
332356 }
@@ -336,18 +360,18 @@ private extension KeyValueEncoder {
336360
337361 let codingPath : [ any CodingKey ]
338362 private let userInfo : [ CodingUserInfoKey : Any ]
339- private let nilEncodingStrategy : NilEncodingStrategy
363+ private let strategy : EncodingStrategy
340364
341- init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , nilEncodingStrategy : NilEncodingStrategy ) {
365+ init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , strategy : EncodingStrategy ) {
342366 self . codingPath = codingPath
343367 self . userInfo = userInfo
344- self . nilEncodingStrategy = nilEncodingStrategy
368+ self . strategy = strategy
345369 }
346370
347371 private var storage : [ EncodedValue ] = [ ]
348372
349373 func getEncodedValue( ) throws -> EncodedValue {
350- return try . value( storage. compactMap { try $0. getValue ( strategy: nilEncodingStrategy ) } )
374+ return try . value( storage. compactMap { try $0. getValue ( strategy: strategy . optionals ) } )
351375 }
352376
353377 public var count : Int {
@@ -431,9 +455,9 @@ private extension KeyValueEncoder {
431455 let encoder = Encoder (
432456 codingPath: codingPath. appending ( index: count) ,
433457 userInfo: userInfo,
434- nilEncodingStrategy : nilEncodingStrategy
458+ strategy : strategy
435459 )
436- if let value = try encoder. encodeToValue ( value) . getValue ( strategy: nilEncodingStrategy ) {
460+ if let value = try encoder. encodeToValue ( value) . getValue ( strategy: strategy . optionals ) {
437461 appendValue ( value)
438462 } else {
439463 appendValue ( . null)
@@ -442,21 +466,21 @@ private extension KeyValueEncoder {
442466
443467 func nestedContainer< NestedKey> ( keyedBy keyType: NestedKey . Type ) -> KeyedEncodingContainer < NestedKey > {
444468 let path = codingPath. appending ( index: count)
445- let keyed = KeyedContainer < NestedKey > ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
469+ let keyed = KeyedContainer < NestedKey > ( codingPath: path, userInfo: userInfo, strategy : strategy )
446470 storage. append ( . provider( keyed. getEncodedValue) )
447471 return KeyedEncodingContainer ( keyed)
448472 }
449473
450474 func nestedUnkeyedContainer( ) -> any UnkeyedEncodingContainer {
451475 let path = codingPath. appending ( index: count)
452- let unkeyed = UnkeyedContainer ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
476+ let unkeyed = UnkeyedContainer ( codingPath: path, userInfo: userInfo, strategy : strategy )
453477 storage. append ( . provider( unkeyed. getEncodedValue) )
454478 return unkeyed
455479 }
456480
457481 func superEncoder( ) -> any Swift . Encoder {
458482 let path = codingPath. appending ( index: count)
459- let encoder = Encoder ( codingPath: path, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
483+ let encoder = Encoder ( codingPath: path, userInfo: userInfo, strategy : strategy )
460484 storage. append ( . provider( encoder. getEncodedValue) )
461485 return encoder
462486 }
@@ -466,12 +490,12 @@ private extension KeyValueEncoder {
466490
467491 let codingPath : [ any CodingKey ]
468492 private let userInfo : [ CodingUserInfoKey : Any ]
469- private let nilEncodingStrategy : NilEncodingStrategy
493+ private let strategy : EncodingStrategy
470494
471- init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , nilEncodingStrategy : NilEncodingStrategy ) {
495+ init ( codingPath: [ any CodingKey ] , userInfo: [ CodingUserInfoKey : Any ] , strategy : EncodingStrategy ) {
472496 self . codingPath = codingPath
473497 self . userInfo = userInfo
474- self . nilEncodingStrategy = nilEncodingStrategy
498+ self . strategy = strategy
475499 }
476500
477501 private var value : EncodedValue ?
@@ -547,8 +571,8 @@ private extension KeyValueEncoder {
547571 return
548572 }
549573
550- let encoder = Encoder ( codingPath: codingPath, userInfo: userInfo, nilEncodingStrategy : nilEncodingStrategy )
551- if let value = try encoder. encodeToValue ( value) . getValue ( strategy: nilEncodingStrategy ) {
574+ let encoder = Encoder ( codingPath: codingPath, userInfo: userInfo, strategy : strategy )
575+ if let value = try encoder. encodeToValue ( value) . getValue ( strategy: strategy . optionals ) {
552576 self . value = . value( value)
553577 } else {
554578 self . value = . null
@@ -557,6 +581,59 @@ private extension KeyValueEncoder {
557581 }
558582}
559583
584+ extension KeyValueEncoder . KeyEncodingStrategy {
585+
586+ func makeStorageKey( for key: String ) -> String {
587+ switch self {
588+ case . useDefaultKeys: return key
589+ case . convertToSnakeCase: return key. toSnakeCase ( )
590+ }
591+ }
592+ }
593+
594+ extension String {
595+
596+ func toSnakeCase( ) -> String {
597+ camelCaseWords
598+ . map { $0. lowercased ( ) }
599+ . joined ( separator: " _ " )
600+ }
601+
602+ var camelCaseWords : [ Substring ] {
603+ var words : [ Range < String . Index > ] = [ ]
604+ var wordStart = startIndex
605+ var searchRange = index ( after: wordStart) ..< endIndex
606+
607+ while let upperCaseRange = self [ searchRange] . rangeOfCharacter ( from: . uppercaseLetters, options: [ ] ) {
608+ let untilUpperCase = wordStart..< upperCaseRange. lowerBound
609+ words. append ( untilUpperCase)
610+
611+ // Find next lowercase character
612+ searchRange = upperCaseRange. lowerBound..< searchRange. upperBound
613+ guard let lowerCaseRange = self [ searchRange] . rangeOfCharacter ( from: . lowercaseLetters, options: [ ] ) else {
614+ // There are no more lower case letters. Just end here.
615+ wordStart = searchRange. lowerBound
616+ break
617+ }
618+
619+ // group runs of multiple capitals together instead of splitting into words
620+ let nextCharacterAfterCapital = self . index ( after: upperCaseRange. lowerBound)
621+ if lowerCaseRange. lowerBound == nextCharacterAfterCapital {
622+ // Next is lowercase
623+ wordStart = upperCaseRange. lowerBound
624+ } else {
625+ // Multiple uppercase in sequence. Stop at the capital before the lower case character.
626+ let beforeLowerIndex = self . index ( before: lowerCaseRange. lowerBound)
627+ words. append ( upperCaseRange. lowerBound..< beforeLowerIndex)
628+ wordStart = beforeLowerIndex
629+ }
630+ searchRange = lowerCaseRange. upperBound..< searchRange. upperBound
631+ }
632+ words. append ( wordStart..< searchRange. upperBound)
633+ return words. map { self [ $0] }
634+ }
635+ }
636+
560637extension Array where Element == any CodingKey {
561638
562639 func appending( key codingKey: any CodingKey ) -> [ any CodingKey ] {
0 commit comments