<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">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.</div><div class=""><br class=""></div><div class="">How about (rough idea):</div><div class=""><br class=""></div><div class="">protocol&nbsp;KeyCodingStrategy {<br class="">&nbsp; &nbsp;&nbsp;func transform(codingKeys:&nbsp;[CodingKey])&nbsp;-&gt;&nbsp;CodingKey<br class="">}<br class=""><br class="">struct&nbsp;SnakeCaseKeyDecodingStrategy {<br class="">&nbsp; &nbsp;&nbsp;func transform(codingKeys:&nbsp;[CodingKey])&nbsp;-&gt;&nbsp;CodingKey {<br class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// implementation<br class="">&nbsp; &nbsp; }<br class="">}<br class=""><br class="">struct&nbsp;SnakeCaseKeyEncodingStrategy {<br class="">&nbsp; &nbsp;&nbsp;func transform(codingKeys:&nbsp;[CodingKey])&nbsp;-&gt;&nbsp;CodingKey {<br class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// implementation<br class="">&nbsp; &nbsp; }<br class="">}<br class=""><br class="">class&nbsp;JSONDecoder {<br class="">&nbsp; &nbsp;&nbsp;/// The strategy to use for decoding keys. Defaults to `nil`, which is the default implementation.<br class="">&nbsp; &nbsp;&nbsp;open&nbsp;var&nbsp;keyDecodingStrategy:&nbsp;KeyCodingStrategy?&nbsp;=&nbsp;nil<br class="">}<br class=""><br class="">class&nbsp;JSONEncoder {<br class="">&nbsp; &nbsp;&nbsp;/// The strategy to use for encoding keys. Defaults to `nil`, which is the default implementation.<br class="">&nbsp; &nbsp;&nbsp;open&nbsp;var&nbsp;keyEncodingStrategy:&nbsp;KeyCodingStrategy?&nbsp;=&nbsp;nil<br class="">}<br class=""><br class="">var&nbsp;decoder&nbsp;=&nbsp;JSONDecoder()<br class="">decoder.keyDecodingStrategy&nbsp;=&nbsp;SnakeCaseKeyDecodingStrategy()<br class="">let&nbsp;result&nbsp;=&nbsp;try!&nbsp;decoder.decode(Thing.self,&nbsp;from:&nbsp;data)</div><div class=""><br class=""></div><div class="">David</div><br class=""><div><blockquote type="cite" class=""><div class="">On 7 Nov 2017, at 18:20, Itai Ferber via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class="">


<meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8" class="">

<div class="">
<div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">Hi Norio,</p><p dir="auto" class="">There are two reasons that I think this is valuable over doing something in <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">CodingKeys</code>:</p>

<ol class="">
<li value="1" class="">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.</li>
<li value="2" class="">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 <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">CodingKeys</code> directly, you wouldn’t be able to perform it on types you don’t directly own.</li>
</ol><p dir="auto" class="">— Itai</p><p dir="auto" class="">On 6 Nov 2017, at 17:39, Norio Nomura via swift-evolution wrote:</p>

</div>
<div style="white-space:normal" class=""></div>
<blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><div id="42AB9958-B5CA-40A3-8C1F-C594DC5BCA0D" class=""><div dir="ltr" class="">Hi Tony,<div class=""><br class=""></div><div class="">Is it better for us to choose on `Codable` side whether `rawValue` of `CodingKeys` should be generated with snake_case?<br class=""></div><div class="">It seems to be more consistent with the current method of setting `rawValue` of `CodingKeys` on `Codable` side.<br class=""></div><div class=""><br class=""></div><div class="">Thanks,</div><div class="">--</div><div class="">@norio_nomura</div></div><div class="gmail_extra"><br class=""><div class="gmail_quote">2017-11-07 5:54 GMT+09:00 Tony Parker via swift-evolution <span dir="ltr" class="">&lt;<a href="mailto:swift-evolution@swift.org" target="_blank" class="">swift-evolution@swift.org</a>&gt;</span>:<br class=""><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word;line-break:after-white-space" class="">Hi everyone,<div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">Link to PR with proposal content:</div><div class=""><br class=""></div><div class=""><a href="https://github.com/apple/swift-corelibs-foundation/pull/1301" target="_blank" class="">https://github.com/apple/<wbr class="">swift-corelibs-foundation/<wbr class="">pull/1301</a></div><div class=""><br class=""></div><div class="">Link to implementation for the overlay:</div><div class=""><br class=""></div><div class=""><a href="https://github.com/apple/swift/pull/12779" target="_blank" class="">https://github.com/apple/<wbr class="">swift/pull/12779</a></div><div class=""><br class=""></div><div class="">Markdown follows.</div><div class=""><br class=""></div><div class="">Thanks,</div><div class="">- Tony</div><div class=""><br class=""></div><div class=""><div class=""># Key Strategies for JSONEncoder and JSONDecoder</div><div class=""><br class=""></div><div class="">* Proposal: SCLF-0001</div><div class="">* Author(s): Tony Parker &lt;<a href="mailto:anthony.parker@apple.com" target="_blank" class="">anthony.parker@apple.com</a>&gt;</div><div class=""><br class=""></div><div class="">##### Related radars or Swift bugs</div><div class=""><br class=""></div><div class="">* &lt;<a class="">rdar://problem/33019707</a>&gt; Snake case / Camel case conversions for JSONEncoder/Decoder</div><div class=""><br class=""></div><div class="">##### Revision history</div><div class=""><br class=""></div><div class="">* **v1** Initial version</div><div class=""><br class=""></div><div class="">## Introduction</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">## Proposed solution</div><div class=""><br class=""></div><div class="">`JSONEncoder` and `JSONDecoder` will gain new strategy properties to allow for conversion of keys during encoding and decoding.</div><div class=""><br class=""></div><div class="">```swift</div><div class="">class JSONDecoder {</div><div class="">&nbsp; &nbsp; /// The strategy to use for automatically changing the value of keys before decoding.</div><div class="">&nbsp; &nbsp; public enum KeyDecodingStrategy {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Use the keys specified by each type. This is the default strategy.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case useDefaultKeys</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Convert from "snake_case_keys" to "camelCaseKeys" before attempting to match a key with the one specified by each type.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///&nbsp;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 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.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Converting from snake case to camel case:</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 1. Capitalizes the word starting after each `_`</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 2. Removes all `_`</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata).</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case convertFromSnakeCase</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 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.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case custom(([CodingKey]) -&gt; CodingKey)</div><div class="">&nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; /// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.</div><div class="">&nbsp; &nbsp; open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys</div><div class="">}</div><div class=""><br class=""></div><div class="">class JSONEncoder {</div><div class="">&nbsp; &nbsp; /// The strategy to use for automatically changing the value of keys before encoding.</div><div class="">&nbsp; &nbsp; public enum KeyEncodingStrategy {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Use the keys specified by each type. This is the default strategy.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case useDefaultKeys</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to JSON payload.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Capital characters are determined by testing membership in `CharacterSet.<wbr class="">uppercaseLetters` and `CharacterSet.<wbr class="">lowercaseLetters` (Unicode General Categories Lu and Lt).</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 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.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Converting from camel case to snake case:</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 1. Splits words at the boundary of lower-case to upper-case</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 2. Inserts `_` between words</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 3. Lowercases the entire string</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 4. Preserves starting and ending `_`.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; ///</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case convertToSnakeCase</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// Provide a custom conversion to the key in the encoded JSON from the keys specified by the encoded types.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; /// 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.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case custom(([CodingKey]) -&gt; CodingKey)</div><div class="">&nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp;&nbsp;</div><div class="">&nbsp; &nbsp; /// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`.</div><div class="">&nbsp; &nbsp; open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys</div><div class="">}</div><div class="">```</div><div class=""><br class=""></div><div class="">## Detailed design</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">Converting from snake case to camel case:</div><div class=""><br class=""></div><div class="">1. Capitalizes the word starting after each `_`</div><div class="">2. Removes all `_`</div><div class="">3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata).</div><div class=""><br class=""></div><div class="">For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`.</div><div class=""><br class=""></div><div class="">Converting from camel case to snake case:</div><div class=""><br class=""></div><div class="">1. Splits words at the boundary of lower-case to upper-case</div><div class="">2. Inserts `_` between words</div><div class="">3. Lowercases the entire string</div><div class="">4. Preserves starting and ending `_`.</div><div class=""><br class=""></div><div class="">For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`.</div><div class=""><br class=""></div><div class="">We also provide a `custom` action for both encoding and decoding to allow for maximum flexibility if the built-in options are not sufficient.</div><div class=""><br class=""></div><div class="">## Example</div><div class=""><br class=""></div><div class="">Given this JSON:</div><div class=""><br class=""></div><div class="">```</div><div class="">{ "hello_world" : 3, "goodbye_cruel_world" : 10, "key" : 42 }</div><div class="">```</div><div class=""><br class=""></div><div class="">Previously, you would customize your `Decodable` type with custom keys, like this:</div><div class=""><br class=""></div><div class="">```swift</div><div class="">struct Thing : Decodable {</div><div class=""><br class=""></div><div class="">&nbsp; &nbsp; let helloWorld : Int</div><div class="">&nbsp; &nbsp; let goodbyeCruelWorld: Int</div><div class="">&nbsp; &nbsp; let key: Int</div><div class=""><br class=""></div><div class="">&nbsp; &nbsp; private enum CodingKeys : CodingKey {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case helloWorld = "hello_world"</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case goodbyeCruelWorld = "goodbye_cruel_world"</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; case key</div><div class="">&nbsp; &nbsp; }</div><div class="">}</div><div class=""><br class=""></div><div class="">var decoder = JSONDecoder()</div><div class="">let result = try! decoder.decode(Thing.self, from: data)</div><div class="">```</div><div class=""><br class=""></div><div class="">With this change, you can write much less boilerplate:</div><div class=""><br class=""></div><div class="">```swift</div><div class="">struct Thing : Decodable {</div><div class=""><br class=""></div><div class="">&nbsp; &nbsp; let helloWorld : Int</div><div class="">&nbsp; &nbsp; let goodbyeCruelWorld: Int</div><div class="">&nbsp; &nbsp; let key: Int</div><div class="">}</div><div class=""><br class=""></div><div class="">var decoder = JSONDecoder()</div><div class="">decoder.keyDecodingStrategy = .convertFromSnakeCase</div><div class="">let result = try! decoder.decode(Thing.self, from: data)</div><div class="">```</div><div class=""><br class=""></div><div class="">## Alternatives considered</div><div class=""><br class=""></div><div class="">None.</div></div><div class=""><br class=""></div></div><br class="">______________________________<wbr class="">_________________<br class="">
swift-evolution mailing list<br class="">
<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank" class="">https://lists.swift.org/<wbr class="">mailman/listinfo/swift-<wbr class="">evolution</a><br class="">
<br class=""></blockquote></div><br class=""></div></div></blockquote>
<div style="white-space:normal" class="">
<blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class="">
</blockquote><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">_______________________________________________<br class="">
swift-evolution mailing list<br class="">
<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" style="color:#777" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a></p>
</blockquote></div>
<div style="white-space:normal" class="">
</div>
</div>
</div>

_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></blockquote></div><br class=""></body></html>