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
> <[email protected]> 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
> <[email protected] <mailto:[email protected]>>:
> 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
> <https://github.com/apple/swift-corelibs-foundation/pull/1301>
>
> Link to implementation for the overlay:
>
> https://github.com/apple/swift/pull/12779
> <https://github.com/apple/swift/pull/12779>
>
> Markdown follows.
>
> Thanks,
> - Tony
>
> # Key Strategies for JSONEncoder and JSONDecoder
>
> * Proposal: SCLF-0001
> * Author(s): Tony Parker <[email protected]
> <mailto:[email protected]>>
>
> ##### 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
> [email protected] <mailto:[email protected]>
> https://lists.swift.org/mailman/listinfo/swift-evolution
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>
>
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution