Hi Tony, Is it better for us to choose on `Codable` side whether `rawValue` of `CodingKeys` should be generated with snake_case? It seems to be more consistent with the current method of setting `rawValue` of `CodingKeys` on `Codable` side.
Thanks, -- @norio_nomura 2017-11-07 5:54 GMT+09:00 Tony Parker via swift-evolution < swift-evolution@swift.org>: > Hi everyone, > > While we have no formal process at this time for proposals of changes to > Foundation-only code, I would still like to post one that we have run > through our internal process here for additional public comment. > > Link to PR with proposal content: > > https://github.com/apple/swift-corelibs-foundation/pull/1301 > > Link to implementation for the overlay: > > https://github.com/apple/swift/pull/12779 > > Markdown follows. > > Thanks, > - Tony > > # Key Strategies for JSONEncoder and JSONDecoder > > * Proposal: SCLF-0001 > * Author(s): Tony Parker <anthony.par...@apple.com> > > ##### Related radars or Swift bugs > > * <rdar://problem/33019707> Snake case / Camel case conversions for > JSONEncoder/Decoder > > ##### Revision history > > * **v1** Initial version > > ## Introduction > > While early feedback for `JSONEncoder` and `JSONDecoder` has been very > positive, many developers have told us that they would appreciate a > convenience for converting between `snake_case_keys` and `camelCaseKeys` > without having to manually specify the key values for all types. > > ## Proposed solution > > `JSONEncoder` and `JSONDecoder` will gain new strategy properties to allow > for conversion of keys during encoding and decoding. > > ```swift > class JSONDecoder { > /// The strategy to use for automatically changing the value of keys > before decoding. > public enum KeyDecodingStrategy { > /// Use the keys specified by each type. This is the default > strategy. > case useDefaultKeys > > /// Convert from "snake_case_keys" to "camelCaseKeys" before > attempting to match a key with the one specified by each type. > /// > /// The conversion to upper case uses `Locale.system`, also known > as the ICU "root" locale. This means the result is consistent regardless of > the current user's locale and language preferences. > /// > /// Converting from snake case to camel case: > /// 1. Capitalizes the word starting after each `_` > /// 2. Removes all `_` > /// 3. Preserves starting and ending `_` (as these are often used > to indicate private variables or other metadata). > /// For example, `one_two_three` becomes `oneTwoThree`. > `_one_two_three_` becomes `_oneTwoThree_`. > /// > /// - Note: Using a key decoding strategy has a nominal > performance cost, as each string key has to be inspected for the `_` > character. > case convertFromSnakeCase > > /// Provide a custom conversion from the key in the encoded JSON > to the keys specified by the decoded types. > /// The full path to the current decoding position is provided for > context (in case you need to locate this key within the payload). The > returned key is used in place of the last component in the coding path > before decoding. > case custom(([CodingKey]) -> CodingKey) > } > > /// The strategy to use for decoding keys. Defaults to > `.useDefaultKeys`. > open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys > } > > class JSONEncoder { > /// The strategy to use for automatically changing the value of keys > before encoding. > public enum KeyEncodingStrategy { > /// Use the keys specified by each type. This is the default > strategy. > case useDefaultKeys > > /// Convert from "camelCaseKeys" to "snake_case_keys" before > writing a key to JSON payload. > /// > /// Capital characters are determined by testing membership in > `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` > (Unicode General Categories Lu and Lt). > /// The conversion to lower case uses `Locale.system`, also known > as the ICU "root" locale. This means the result is consistent regardless of > the current user's locale and language preferences. > /// > /// Converting from camel case to snake case: > /// 1. Splits words at the boundary of lower-case to upper-case > /// 2. Inserts `_` between words > /// 3. Lowercases the entire string > /// 4. Preserves starting and ending `_`. > /// > /// For example, `oneTwoThree` becomes `one_two_three`. > `_oneTwoThree_` becomes `_one_two_three_`. > /// > /// - Note: Using a key encoding strategy has a nominal > performance cost, as each string key has to be converted. > case convertToSnakeCase > > /// Provide a custom conversion to the key in the encoded JSON > from the keys specified by the encoded types. > /// The full path to the current encoding position is provided for > context (in case you need to locate this key within the payload). The > returned key is used in place of the last component in the coding path > before encoding. > case custom(([CodingKey]) -> CodingKey) > } > > > /// The strategy to use for encoding keys. Defaults to > `.useDefaultKeys`. > open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys > } > ``` > > ## Detailed design > > The strategy enum allows developers to pick from common actions of > converting to and from `snake_case` to the Swift-standard `camelCase`. The > implementation is intentionally simple, because we want to make the rules > predictable. > > Converting from snake case to camel case: > > 1. Capitalizes the word starting after each `_` > 2. Removes all `_` > 3. Preserves starting and ending `_` (as these are often used to indicate > private variables or other metadata). > > For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` > becomes `_oneTwoThree_`. > > Converting from camel case to snake case: > > 1. Splits words at the boundary of lower-case to upper-case > 2. Inserts `_` between words > 3. Lowercases the entire string > 4. Preserves starting and ending `_`. > > For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` > becomes `_one_two_three_`. > > We also provide a `custom` action for both encoding and decoding to allow > for maximum flexibility if the built-in options are not sufficient. > > ## Example > > Given this JSON: > > ``` > { "hello_world" : 3, "goodbye_cruel_world" : 10, "key" : 42 } > ``` > > Previously, you would customize your `Decodable` type with custom keys, > like this: > > ```swift > struct Thing : Decodable { > > let helloWorld : Int > let goodbyeCruelWorld: Int > let key: Int > > private enum CodingKeys : CodingKey { > case helloWorld = "hello_world" > case goodbyeCruelWorld = "goodbye_cruel_world" > case key > } > } > > var decoder = JSONDecoder() > let result = try! decoder.decode(Thing.self, from: data) > ``` > > With this change, you can write much less boilerplate: > > ```swift > struct Thing : Decodable { > > let helloWorld : Int > let goodbyeCruelWorld: Int > let key: Int > } > > var decoder = JSONDecoder() > decoder.keyDecodingStrategy = .convertFromSnakeCase > let result = try! decoder.decode(Thing.self, from: data) > ``` > > ## Alternatives considered > > None. > > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution