[swift-evolution] JSONEncoder: Key strategies

Norio Nomura norio.nomura at gmail.com
Tue Nov 7 18:48:31 CST 2017


Hi Itai,

I see. I thought that the second reason you mentioned is so reasonable.
Apart from this proposal, if enum with String as RawValue is defined by
omitting StringLiteral, it would be a good idea to have a mechanism to
generate snake_case or a space delimited string.


2017-11-08 2:20 GMT+09:00 Itai Ferber <iferber at apple.com>:

> Hi Norio,
>
> There are two reasons that I think this is valuable over doing something
> in CodingKeys:
>
>    1. The definition you give your coding keys affects all encoding
>    formats. JSON is a format where snake_case can be relatively common, so the
>    transformation makes a lot of sense there. For other formats, like plist
>    files or otherwise, the transformation might not make as much sense.
>    Instead of affecting all of your coding keys globally, this limits it to
>    JSON.
>    2. More importantly, this allows you to transform keys of things which
>    you don’t necessarily own. If you’re working with types that you didn’t
>    write (but which are expected to have snake_case keys nonetheless), this
>    allows you to perform that transformation. If this were instead an
>    annotation on CodingKeys directly, you wouldn’t be able to perform it
>    on types you don’t directly own.
>
> — Itai
>
> On 6 Nov 2017, at 17:39, Norio Nomura via swift-evolution wrote:
>
> 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 at 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.parker at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171108/740f235b/attachment.html>


More information about the swift-evolution mailing list