JSONEncoder: Key strategies


(Tony Parker) #1

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@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.

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:

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:

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.


Handling edge cases in JSONEncoder/JSONDecoder key conversion strategies (SR-6629)
(Alejandro Martinez) #2

I’m in favor of this as it’s really, but specially the custom strategy. And thinking about that, would there be a way to expose the functionality of converting a string to camel case or snake case so it could be used in case of writing a custom strategy. Would be cool to have the original functionality when writings a custom strategy instead of having to reimplement it if there is a need.

···

Sent from my iPad

On 6 Nov 2017, at 20:54, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

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@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.

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:

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:

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


(Norio Nomura) #3

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.parker@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.

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:

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:

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


(Zach Wolfe) #4

+1. Would allow me to delete a ton of code in my current project.

···

On Nov 6, 2017, at 2:54 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

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@apple.com <mailto:anthony.parker@apple.com>>

##### Related radars or Swift bugs

* <rdar://problem/33019707 <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.

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:

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:

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


(Brent Royal-Gordon) #5

My first thought was "are you handling `valueAsHTML` correctly?", but it looks like you are with the "boundary of lower-case to upper-case" wording. But what do you plan to do for numbers? Characters in caseless scripts? Emoji (which are valid in Swift identifiers)? I don't necessarily have strong opinions about the right answer—just want to make sure you do *something* about it.

···

On Nov 6, 2017, at 12:54 PM, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

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_`.

--
Brent Royal-Gordon
Architechies


(Rod Brown) #6

I’m a fan. I understand that the CodingKey was the initial thought behind how to customise this, but a transformative approach is nice where there is a patterned update to keys.

Just one question: is there much performance impact by running through a key encoding strategy, even if it’s just a return in the case of `useDefaultKeys`?

···

On 7 Nov 2017, at 7:54 am, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

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@apple.com <mailto:anthony.parker@apple.com>>

##### Related radars or Swift bugs

* <rdar://problem/33019707 <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.

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:

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:

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


(Tony Parker) #7

Hi Alejandro,

I’m in favor of this as it’s really, but specially the custom strategy. And thinking about that, would there be a way to expose the functionality of converting a string to camel case or snake case so it could be used in case of writing a custom strategy. Would be cool to have the original functionality when writings a custom strategy instead of having to reimplement it if there is a need.

Sent from my iPad

We talked about this a bit, and while there are some clever things that are possible, ultimately we felt it made the API too complex. I think we have room to provide the functionality on the enum later if we want (e.g. a function you could call).

- Tony

···

On Nov 6, 2017, at 3:14 PM, Alejandro Martinez via swift-evolution <swift-evolution@swift.org> wrote:

On 6 Nov 2017, at 20:54, Tony Parker via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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@apple.com <mailto:anthony.parker@apple.com>>

##### Related radars or Swift bugs

* <rdar://problem/33019707 <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.

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:

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:

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 <mailto: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


(Alejandro Martinez) #8

Yeha the API surface is a good concern. I was wondering if it would make sense to be part of String for example instead of tying it to the encoders api. I guess add that point it will be a different discussion.
In any case, good proposal :wink:

···

Sent from my iPad

On 7 Nov 2017, at 00:35, Tony Parker <anthony.parker@apple.com> wrote:

Hi Alejandro,

On Nov 6, 2017, at 3:14 PM, Alejandro Martinez via swift-evolution <swift-evolution@swift.org> wrote:

I’m in favor of this as it’s really, but specially the custom strategy. And thinking about that, would there be a way to expose the functionality of converting a string to camel case or snake case so it could be used in case of writing a custom strategy. Would be cool to have the original functionality when writings a custom strategy instead of having to reimplement it if there is a need.

Sent from my iPad

We talked about this a bit, and while there are some clever things that are possible, ultimately we felt it made the API too complex. I think we have room to provide the functionality on the enum later if we want (e.g. a function you could call).

- Tony

On 6 Nov 2017, at 20:54, Tony Parker via swift-evolution <swift-evolution@swift.org> wrote:

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@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.

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:

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:

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


(Itai Ferber) #9

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@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@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.

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:

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:

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


(David Hart) #10

But the transformation of the string representation of keys is a feature which other coders/decoders could use, so it might be worth pulling it out of the JSONDecoder/JSONEncoder namespace and make the implementation callable. For example, I could imagine wanting to use a similar system with property lists and xml files.

How about (rough idea):

protocol KeyCodingStrategy {
    func transform(codingKeys: [CodingKey]) -> CodingKey
}

struct SnakeCaseKeyDecodingStrategy {
    func transform(codingKeys: [CodingKey]) -> CodingKey {
        // implementation
    }
}

struct SnakeCaseKeyEncodingStrategy {
    func transform(codingKeys: [CodingKey]) -> CodingKey {
        // implementation
    }
}

class JSONDecoder {
    /// The strategy to use for decoding keys. Defaults to `nil`, which is the default implementation.
    open var keyDecodingStrategy: KeyCodingStrategy? = nil
}

class JSONEncoder {
    /// The strategy to use for encoding keys. Defaults to `nil`, which is the default implementation.
    open var keyEncodingStrategy: KeyCodingStrategy? = nil
}

var decoder = JSONDecoder()
decoder.keyDecodingStrategy = SnakeCaseKeyDecodingStrategy()
let result = try! decoder.decode(Thing.self, from: data)

David

···

On 7 Nov 2017, at 18:20, Itai Ferber via swift-evolution <swift-evolution@swift.org> wrote:

Hi Norio,

There are two reasons that I think this is valuable over doing something in CodingKeys:

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.
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@swift.org <mailto: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.parker@apple.com <mailto:anthony.parker@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.

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:

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:

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 <mailto: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
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Norio Nomura) #11

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@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@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@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.

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:

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:

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


(Itai Ferber) #12

Hi Brent,

Perhaps the wording would be better phrased as "boundary from non-uppercase-character to uppercase-character", i.e. numbers and Emoji are treated the same as lowercase characters and are included in the original word.
The following are [unit test cases from the associated PR](https://github.com/apple/swift/pull/12779/files#diff-26b09c16508c21f9f59dcf6c7a41d4b4R422), which should indicate the behavior implemented here:

let toSnakeCaseTests = [
   ("simpleOneTwo", "simple_one_two"),
   ("myURL", "my_url"),
   ("singleCharacterAtEndX", "single_character_at_end_x"),
   ("thisIsAnXMLProperty", "this_is_an_xml_property"),
   ("single", "single"), // no underscore
   ("", ""), // don't die on empty string
   ("a", "a"), // single character
   ("aA", "a_a"), // two characters
   ("version4Thing", "version4_thing"), // numerics
   ("partCAPS", "part_caps"), // only insert underscore before first all caps
   ("partCAPSLowerAGAIN", "part_caps_lower_again"), // switch back and forth caps.
   ("manyWordsInThisThing", "many_words_in_this_thing"), // simple lowercase underscore more
   ("asdfĆqer", "asdf_ćqer"),
   ("already_snake_case", "already_snake_case"),
   ("dataPoint22", "data_point22"),
   ("dataPoint22Word", "data_point22_word"),
   ("_oneTwoThree", "_one_two_three"),
   ("oneTwoThree_", "one_two_three_"),
   ("__oneTwoThree", "__one_two_three"),
   ("oneTwoThree__", "one_two_three__"),
   ("_oneTwoThree_", "_one_two_three_"),
   ("__oneTwoThree", "__one_two_three"),
   ("__oneTwoThree__", "__one_two_three__"),
   ("_test", "_test"),
   ("_test_", "_test_"),
   ("__test", "__test"),
   ("test__", "test__"),
   ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this
   ("🐧🐟", "🐧🐟") // fishy emoji example?
]

And for completeness, the [complementary test cases](https://github.com/apple/swift/pull/12779/files#diff-26b09c16508c21f9f59dcf6c7a41d4b4R540):

let fromSnakeCaseTests = [
   ("", ""), // don't die on empty string
   ("a", "a"), // single character
   ("ALLCAPS", "ALLCAPS"), // If no underscores, we leave the word as-is
   ("ALL_CAPS", "allCaps"), // Conversion from screaming snake case
   ("single", "single"), // do not capitalize anything with no underscore
   ("snake_case", "snakeCase"), // capitalize a character
   ("one_two_three", "oneTwoThree"), // more than one word
   ("one_2_three", "one2Three"), // numerics
   ("one2_three", "one2Three"), // numerics, part 2
   ("snake_Ćase", "snakeĆase"), // do not further modify a capitalized diacritic
   ("snake_ćase", "snakeĆase"), // capitalize a diacritic
   ("alreadyCamelCase", "alreadyCamelCase"), // do not modify already camel case
   ("__this_and_that", "__thisAndThat"),
   ("_this_and_that", "_thisAndThat"),
   ("this__and__that", "thisAndThat"),
   ("this_and_that__", "thisAndThat__"),
   ("this_aNd_that", "thisAndThat"),
   ("_one_two_three", "_oneTwoThree"),
   ("one_two_three_", "oneTwoThree_"),
   ("__one_two_three", "__oneTwoThree"),
   ("one_two_three__", "oneTwoThree__"),
   ("_one_two_three_", "_oneTwoThree_"),
   ("__one_two_three", "__oneTwoThree"),
   ("__one_two_three__", "__oneTwoThree__"),
   ("_test", "_test"),
   ("_test_", "_test_"),
   ("__test", "__test"),
   ("test__", "test__"),
   ("_", "_"),
   ("__", "__"),
   ("___", "___"),
   ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this
   ("🐧_🐟", "🐧🐟") // fishy emoji example?
]

— Itai

···

On 9 Nov 2017, at 5:57, Brent Royal-Gordon via swift-evolution wrote:

> On Nov 6, 2017, at 12:54 PM, Tony Parker via swift-evolution > <swift-evolution@swift.org> wrote:

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_`.

My first thought was "are you handling `valueAsHTML` correctly?", but it looks like you are with the "boundary of lower-case to upper-case" wording. But what do you plan to do for numbers? Characters in caseless scripts? Emoji (which are valid in Swift identifiers)? I don't necessarily have strong opinions about the right answer—just want to make sure you do *something* about it.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #13

Hi Brent,

Perhaps the wording would be better phrased as "boundary from non-uppercase-character to uppercase-character", i.e. numbers and Emoji are treated the same as lowercase characters and are included in the original word.

Okay, that makes sense.

The following are unit test cases from the associated PR <https://github.com/apple/swift/pull/12779/files#diff-26b09c16508c21f9f59dcf6c7a41d4b4R422>, which should indicate the behavior implemented here:

These look great. It's almost like your team is good at their jobs or something. ;^)

···

On Nov 9, 2017, at 10:00 AM, Itai Ferber <iferber@apple.com> wrote:

--
Brent Royal-Gordon
Architechies


(Morten Bek Ditlevsen) #15

I only just noticed that this is in Swift 4.1! Yay!

Thank you very much for this! :-)

Sincerely,
/morten


(Tony Parker) #16

Great, I’m glad you found it useful!