Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The `convertFromSnakeCase` strategy doesn't work with custom `CodingKeys` in Swift

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?

like image 246
Howard Avatar asked Apr 17 '18 15:04

Howard


1 Answers

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
  }
}
like image 100
Hamish Avatar answered Nov 06 '22 04:11

Hamish