Problema
Cuando integramos APIs REST, es muy común que las claves JSON vengan en snake_case (por ejemplo, first_name) mientras que en Swift modelamos las propiedades en camelCase (firstName).
Si no configuramos nada, nos toca escribir CodingKeys a mano en cada modelo o aceptar errores de decodificación.
Buscamos una forma centralizada y reutilizable de:
- Decodificar JSON sin CodingKeys manuales.
- Codificar nuestros modelos al enviar datos.
- Mantener el mismo comportamiento tanto en apps iOS/macOS (URLSession) como en Vapor (req/res content).
Solución
Creamos extensiones de JSONDecoder y JSONEncoder que exponen constructores de conveniencia y atajos estáticos snakeCase.
Ventajas:
- Cero boilerplate en los modelos: evita CodingKeys repetitivas.
- Nombre autoexplicativo al usarlas: JSONDecoder.snakeCase / JSONEncoder.snakeCase.
- Consistencia en todo el proyecto (cliente y servidor).
extension JSONDecoder {
convenience init(keyDecodingStrategy: KeyDecodingStrategy) {
self.init()
self.keyDecodingStrategy = keyDecodingStrategy
}
static var snakeCase: JSONDecoder {
.init(keyDecodingStrategy: .convertFromSnakeCase)
}
}
extension JSONEncoder {
convenience init(keyEncodingStrategy: KeyEncodingStrategy) {
self.init()
self.keyEncodingStrategy = keyEncodingStrategy
}
static var snakeCase: JSONEncoder {
.init(keyEncodingStrategy: .convertToSnakeCase)
}
}
Nota: si un modelo necesita un nombre de clave específico, puedes seguir usando CodingKeys localmente; la estrategia snake_case actuará como valor por defecto.
Resultado
Ejemplos de uso
// iOS / macOS: leer datos
let data: Data = ...
let user = try JSONDecoder.snakeCase
.decode(UserDTO.self, from: data)
// iOS / macOS: enviar datos
let body = CreateUserDTO(firstName: "Ada", lastName: "Lovelace")
request.httpBody = try JSONEncoder.snakeCase.encode(body)
// Vapor
let input = try req.content
.decode(CreateUserDTO.self, using: JSONDecoder.snakeCase)
Con estos atajos obtenemos:
- Menos errores y mayor legibilidad: las propiedades permanecen en camelCase idiomático Swift.
- Interoperabilidad inmediata con APIs legacy en snake_case.
- Configuración única y reutilizable en todo el proyecto (tests incluidos).
Esto estandariza cómo serializamos/parseamos JSON sin sacrificar claridad ni control fino cuando hace falta.
Keep coding, keep running 🏃♂️