20

I try to use Swift 4.1's new feature to convert snake-case to camelCase during JSON decoding.

Here is the example:

struct StudentInfo: Decodable {
    internal let studentID: String
    internal let name: String
    internal let testScore: String

    private enum CodingKeys: String, CodingKey {
        case studentID = "student_id"
        case name
        case testScore
    }
}

let jsonString = """
{"student_id":"123","name":"Apple Bay Street","test_score":"94608"}
"""

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let decoded = try decoder.decode(StudentInfo.self, from: Data(jsonString.utf8))
    print(decoded)
} catch {
    print(error)
}

I need provide custom CodingKeys since the convertFromSnakeCase strategy can't infer capitalization for acronyms or initialisms (such as studentID) but I expect the convertFromSnakeCase strategy will still work for testScore. However, the decoder throws error ("No value associated with key CodingKeys") and it seems that I can't use convertFromSnakeCase strategy and custom CodingKeys at the same time. Am I missing something?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Howard
  • 268
  • 2
  • 7
  • 7
    You want `case studentID = "studentId"` (compare https://stackoverflow.com/a/44396824/2976878) – the decoder applies the key strategy before consulting the coding keys, so it transforms `"student_id"` to `"studentId"`. – Hamish Apr 17 '18 at 15:09
  • Thank you @Hamish! That works! – Howard Apr 17 '18 at 15:21
  • 1
    @Rob Will do when I get a moment (if nobody else posts an answer in the mean time, that is) – Hamish Apr 17 '18 at 15:50

1 Answers1

37

The key strategies for JSONDecoder (and JSONEncoder) are applied to all keys in the payload – including those that you provide a custom coding key for. When decoding, the JSON key will first be mapped using the given key strategy, and then the decoder will consult the CodingKeys for the given type being decoded.

In your case, the student_id key in your JSON will be mapped to studentId by .convertFromSnakeCase. The exact algorithm for the transformation is given in the documentation:

  1. Capitalize each word that follows an underscore.

  2. Remove all underscores that aren't at the very start or end of the string.

  3. Combine the words into a single string.

The following examples show the result of applying this strategy:

fee_fi_fo_fum

    Converts to: feeFiFoFum

feeFiFoFum

    Converts to: feeFiFoFum

base_uri

    Converts to: baseUri

You therefore need to update your CodingKeys to match this:

internal struct StudentInfo: Decodable, Equatable {
  internal let studentID: String
  internal let name: String
  internal let testScore: String

  private enum CodingKeys: String, CodingKey {
    case studentID = "studentId"
    case name
    case testScore
  }
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 1
    Woah! Many thanks for pointing out the ORDER of the conversions! – Carsten May 04 '19 at 18:50
  • Really helpful answer, although the result is that I'm not going to use `keyDecodingStrategy` because it makes the code considerably less readable. – alex bird Jan 24 '20 at 13:19
  • The way this works is terrible. Custom coding keys should be treated exactly as that: _custom_. – Arjan Jan 05 '23 at 16:34