Codable snake case conversion

I have an address type with a property address1 that I'm populating from an API. The API uses snake case and names this property address_1. I've used CodingKeys to specify the key name, and it creates JSON with the specified key name but doesn't read it.

Is there any way to get a snake case conversion from address_1 to work? How can I tell what key Swift's Decoder is looking for?

import Foundation

struct Address : Codable {
	var address1 : String
	var city : String
	var state : String
	var postalCode : String
	
	enum CodingKeys: String, CodingKey {
		case address1 = "address_1"
		case city, state, postalCode
	}
}

var addr = Address(address1: "123 Anywhere Ln", city: "Vero Beach", state: "FL", postalCode: "32968")

let encoder1 = JSONEncoder()
encoder1.keyEncodingStrategy = .convertToSnakeCase
let data1 = try encoder1.encode(addr)
print(String(data: data1, encoding: .utf8)!)
=> {"address_1":"123 Anywhere Ln","state":"FL","city":"Vero Beach","postal_code":"32968"}

let encoder2 = JSONEncoder()
let data2 = try encoder2.encode(addr)
print(String(data: data2, encoding: .utf8)!)
=>  {"address_1":"123 Anywhere Ln","state":"FL","city":"Vero Beach","postalCode":"32968"}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var addr2 = try decoder.decode(Address.self, from: data1)
print(addr2)
=> Fatal error: Error raised at top level: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "address_1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"address_1\", intValue: nil) (\"address_1\").", underlyingError: nil)): file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/ErrorType.swift, line 200

I think you shot yourself in the foot there. By specifying the actual key in CodingKeys you eliminated the need for the .convertFromSnakeCase decoding strategy.

I don't know for sure, but my guess is that the strategy causes the incoming JSON key "address_1" to first be converted to "address1", at which point it no longer matches the key you said you're looking for, i.e. "address_1". Hence, the error that no value is associated with key "address_1".

In the other direction, converting "address_1" to snake case does nothing, so it seems to work correctly.

At least, that's what it looks like to me.

@QuinceyMorris has it exactly right: the .convertFromSnakeCase decoding strategy

converts snake-case keys to camel-case keys

i.e., upon encountering a snake_case key in the payload, looks up the camelCase equivalent in the CodingKeys enum. Because you're assigning a snake_case value for the address1 key already, no camelCase key can be found.

In this case, you can either continue using .convertToSnakeCase/.convertFromSnakeCase and dropping the custom key for address1 (in this case, it means you could drop the whole CodingKeys enum entirely since you're not customizing any other keys), or assigning snake_case keys to all of the properties you care about and dropping the strategy.

Hmm...I must have had another problem too somewhere along the line. Removing the custom CodingKeys made it work, just as you said.

I had been looking for the snake case conversion rules, I knew I had seen it before but somehow couldn't find it last night when I needed it.

Thanks for the help!