Swift 4 agregó el nuevo Codable
protocolo. Cuando lo uso JSONDecoder
, parece requerir que todas las propiedades no opcionales de mi Codable
clase tengan claves en el JSON o arroja un error.
Hacer que cada propiedad de mi clase sea opcional parece una molestia innecesaria ya que lo que realmente quiero es usar el valor en el json o un valor predeterminado. (No quiero que la propiedad sea nula).
¿Hay alguna forma de hacer esto?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
Respuestas:
El enfoque que prefiero es el uso de los llamados DTO: objeto de transferencia de datos. Es una estructura que se ajusta a Codable y representa el objeto deseado.
Luego, simplemente inicie el objeto que desea usar en la aplicación con ese DTO.
Este enfoque también es bueno ya que puede renombrar y cambiar el objeto final como desee. Es claro y requiere menos código que la decodificación manual. Además, con este enfoque, puede separar la capa de red de otra aplicación.
fuente
Puede implementar el
init(from decoder: Decoder)
método en su tipo en lugar de usar la implementación predeterminada:También puede hacer
name
una propiedad constante (si lo desea):o
Re tu comentario: con una extensión personalizada
podría implementar el método init como
pero eso no es mucho más corto que
fuente
CodingKeys
enumeración generada automáticamente (por lo que puede eliminar la definición personalizada) :)ObjectMapper
maneja esto muy bien.Decodable
y también está proporcionando su propia implementacióninit(from:)
? En ese caso, el compilador asume que desea manejar la decodificación manualmente usted mismo y, por lo tanto, no sintetiza unaCodingKeys
enumeración por usted. Como usted dice, enCodable
cambio , conformarse a funciona porque ahora el compilador está sintetizandoencode(to:)
para usted y también sintetizaCodingKeys
. Si también proporciona su propia implementación deencode(to:)
,CodingKeys
ya no se sintetizará.Una solución sería utilizar una propiedad calculada que tenga como valor predeterminado el valor deseado si no se encuentra la clave JSON. Esto agrega un poco de verbosidad adicional, ya que deberá declarar otra propiedad y requerirá agregar la
CodingKeys
enumeración (si no está ya allí). La ventaja es que no es necesario escribir código de decodificación / codificación personalizado.Por ejemplo:
fuente
Puede implementar.
fuente
Si no desea implementar sus métodos de codificación y decodificación, existe una solución algo sucia en torno a los valores predeterminados.
Puede declarar su nuevo campo como opcional sin empaquetar implícitamente y verificar si es nulo después de la decodificación y establecer un valor predeterminado.
Probé esto solo con PropertyListEncoder, pero creo que JSONDecoder funciona de la misma manera.
fuente
Si cree que escribir su propia versión de
init(from decoder: Decoder)
es abrumador, le aconsejo que implemente un método que verifique la entrada antes de enviarla al decodificador. De esa manera, tendrá un lugar donde puede verificar la ausencia de campos y establecer sus propios valores predeterminados.Por ejemplo:
Y para iniciar un objeto desde json, en lugar de:
Init se verá así:
En esta situación particular, prefiero tratar con opcionales, pero si tiene una opinión diferente, puede hacer que su método customDecode (:) sea arrojable
fuente
Me encontré con esta pregunta buscando exactamente lo mismo. Las respuestas que encontré no fueron muy satisfactorias aunque temía que las soluciones aquí fueran la única opción.
En mi caso, la creación de un decodificador personalizado requeriría una tonelada de texto estándar que sería difícil de mantener, así que seguí buscando otras respuestas.
Me encontré con este artículo que muestra una forma interesante de superar esto en casos simples usando un archivo
@propertyWrapper
. Lo más importante para mí fue que era reutilizable y requería una refactorización mínima del código existente.El artículo asume un caso en el que querría que una propiedad booleana faltante se estableciera de forma predeterminada en falso sin fallar, pero también muestra otras variantes diferentes. Puede leerlo con más detalle, pero mostraré lo que hice para mi caso de uso.
En mi caso, tenía un
array
mensaje que quería inicializar como vacío si faltaba la clave.Entonces, declaré las siguientes
@propertyWrapper
extensiones adicionales:La ventaja de este método es que puede solucionar fácilmente el problema en el código existente simplemente agregando el
@propertyWrapper
a la propiedad. En mi caso:Espero que esto ayude a alguien a lidiar con el mismo problema.
ACTUALIZAR:
Después de publicar esta respuesta mientras continuaba investigando el asunto, encontré este otro artículo, pero lo más importante es la biblioteca respectiva que contiene algunos correos electrónicos comunes fáciles de usar
@propertyWrapper
para este tipo de casos:https://github.com/marksands/BetterCodable
fuente