bottom bar ios json swift rest optional

ios - bottom - status bar iphone



¿Debo usar opcional para las propiedades de los modelos de objetos que se analizarán desde JSON? (11)

Mi aplicación iOS tiene una configuración bastante común: realiza consultas HTTP a un servidor API que responde con objetos JSON. Estos objetos JSON se analizan a los objetos Swift apropiados.

Inicialmente, dividí las propiedades en propiedades requeridas y propiedades opcionales, principalmente basadas en los requisitos de la base de datos de mi servidor API. Por ejemplo, los campos id , email y name son obligatorios, por lo que utilizan tipos no opcionales. Otros pueden ser NULL en la base de datos, por lo que son tipos opcionales.

class User { let id: Int let email: String let profile: String? let name: String let motive: String? let address: String? let profilePhotoUrl: String? }

Recientemente, comencé a preguntarme si era una buena configuración. Descubrí que aunque algunas propiedades pueden estar siempre en la base de datos, eso no significa que esas propiedades siempre se incluirán en la respuesta JSON.

Por ejemplo, en la página de Perfil de usuario, todos estos campos son necesarios para mostrar correctamente la vista. Por lo tanto, la respuesta JSON incluirá todos estos campos. Sin embargo, para una vista que enumera los nombres de los usuarios, no necesitaría un email ni una id , y la respuesta JSON probablemente tampoco debería incluir esas propiedades. Desafortunadamente, esto causará un error y colapsará la aplicación al analizar la respuesta JSON en el objeto Swift, ya que la aplicación espera que el id , el email y el name sean siempre nulos.

Estoy pensando en cambiar todas las propiedades de los objetos Swift en opcionales, pero se siente como si desechara todos los beneficios de esta característica específica del lenguaje. Además, tendré que escribir muchas más líneas de código para desenvolver todas estas opciones en otro lugar en la aplicación de todos modos.

Por otro lado, los objetos JSON no son, por su naturaleza, muy interoperables con una tipificación estática estricta y una comprobación nula de Swift, por lo que sería mejor simplemente aceptar esa molestia.

¿Debo hacer la transición a modelos con todas las propiedades como opcionales? ¿O hay un mejor camino? Apreciaría cualquier comentario aquí.


En mi opinión, elegiré 1 de 2 soluciones:

  1. Edite mi init func de JSON a object , inicie con valores de objeto predeterminados para todas las propiedades ( id = -1, email = '''' ), luego lea JSON con verificación opcional.
  2. Crea una nueva class/struct para ese caso específico.

Esto realmente depende de la forma en que maneja sus datos. Si está manejando sus datos a través de una clase "Codable", entonces tiene que escribir un decodificador personalizado para lanzar una excepción cuando no obtenga ciertos valores esperados. Al igual que:

class User: Codable { let id: Int let email: String let profile: String? let name: String let motive: String? let address: String? let profilePhotoUrl: String? //other methods (such as init, encoder, and decoder) need to be added below. }

Porque sé que voy a tener que devolver un error si no obtengo los parámetros mínimos requeridos, necesitaría algo como una enumeración de Error:

enum UserCodableError: Error { case missingNeededParameters //and so on with more cases }

Debe utilizar claves de codificación para mantener las cosas coherentes desde el servidor. Una forma de hacerlo dentro del objeto de usuario sería así:

fileprivate enum CodingKeys: String, CodingKey { case id = "YOUR JSON SERVER KEYS GO HERE" case email case profile case name case motive case address case profilePhotoUrl }

Entonces, necesitas escribir tu decodificador. Una forma de hacerlo sería así:

required init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) guard let id = try? values.decode(Int.self, forKey: .id), let email = try? values.decode(String.self, forKey: .email), let name = try? values.decode(String.self, forKey: .name) else { throw UserCodableError.missingNeededParameters } self.id = id self.email = email self.name = name //now simply try to decode the optionals self.profile = try? values.decode(String.self, forKey: .profile) self.motive = try? values.decode(String.self, forKey: .motive) self.address = try? values.decode(String.self, forKey: .address) self.profilePhotoUrl = try? values.decode(String.self, forKey: .profilePhotoUrl) }

ALGO QUE DEBE TENER EN CUENTA: también debe escribir su propio codificador para mantener la coherencia.

Todo eso puede ir, simplemente a una declaración de llamada agradable como esta:

if let user = try? JSONDecoder().decode(User.self, from: jsonData) { //do stuff with user }

Esta es probablemente la forma más segura, rápida y orientada a objetos para manejar este tipo de problemas.


Hay tres formas en que puedes ir con esto:

  1. Envíe siempre todos los datos JSON y deje sus propiedades no opcionales.

  2. Hacer todas las propiedades opcionales.

  3. Haga que todas las propiedades no sean opcionales y escriba su propio método init(from:) para asignar valores predeterminados a los valores faltantes, como se describe en esta respuesta .

Todo esto debería funcionar; el que es "mejor" se basa en la opinión y, por lo tanto, está fuera del alcance de una respuesta de desbordamiento de pila. Elija el que sea más conveniente para su necesidad particular.


La premisa:

Representación parcial es un patrón común en REST. ¿Eso significa que todas las propiedades en Swift deben ser opcionales? Por ejemplo, el cliente podría necesitar una lista de identificadores de usuario para una vista. ¿Eso significa que todas las demás propiedades (nombre, correo electrónico, etc.) deben marcarse como opcionales? ¿Es esta buena práctica en Swift?

Las propiedades de marcado opcionales en un modelo solo indican que la clave puede venir o no. Le permite al lector saber ciertas cosas sobre el modelo en la primera mirada.
Si mantiene solo un modelo común para diferentes estructuras de respuesta API y hace que todas las propiedades sean opcionales, es muy discutible que sea una buena práctica o no.
He hecho esto y me muerde. A veces está bien, a veces no está lo suficientemente claro.

Mantener un modelo para múltiples API es como diseñar un ViewController con muchos elementos de la interfaz de usuario y, dependiendo de los casos particulares, determinar qué elemento de la interfaz de usuario debe mostrarse o no.
Esto aumenta la curva de aprendizaje para los nuevos desarrolladores, ya que hay más comprensión del sistema involucrada.

Mis 2 centavos en esto:

Suponiendo que vamos a seguir adelante con el Codable de Swift para codificar / decodificar modelos, lo Codable en modelos separados en lugar de mantener un modelo común con todas las opciones y / o valores predeterminados.

Las razones de mi decisión son:

  1. Claridad de separación

    • Cada modelo para un propósito específico.
    • Alcance de decodificadores personalizados más limpios.
      • Útil cuando la estructura json necesita un poco de preprocesamiento
  2. Consideración de las claves adicionales específicas de la API que podrían venir más adelante.

    • ¿Qué sucede si esta API de la lista de usuarios es la única que requiere más claves como, por ejemplo, número de amigos o alguna otra estadística?
      • ¿Debo seguir cargando un solo modelo para admitir diferentes casos con claves adicionales que vienen solo en una respuesta API pero no en otra?
      • ¿Qué pasa si una tercera API está diseñada para obtener información del usuario pero esta vez con un propósito ligeramente diferente? ¿Debo sobrecargar el mismo modelo con más teclas?
    • Con un modelo único, a medida que el proyecto continúa avanzando, las cosas podrían complicarse, ya que la disponibilidad clave ahora se basa en mayúsculas y minúsculas. Con todas las opciones, tendremos un montón de enlaces opcionales y quizás algunos atajos de fusión nula aquí y allá que podríamos haber evitado con modelos dedicados en primer lugar.
  3. Escribir un modelo es barato, pero mantener los casos no lo es.

Sin embargo, si fuera perezoso y tengo la sensación de que no se avecinan cambios locos, seguiré adelante haciendo todas las opciones opcionales y asumiré los costos asociados.


Lo primero que debe hacer es preguntar: ¿Un elemento de la "vista que enumera los nombres de los usuarios" debe ser el mismo tipo de objeto que el objeto modelo detrás de una "página de perfil de usuario"? Talvez no. Tal vez debería crear un modelo específicamente para la lista de usuarios:

struct UserList: Decodable { struct Item: Decodable { var id: Int var name: String } var items: [Item] }

(Aunque la pregunta dijo que la respuesta JSON podría no incluir la id , no parece que una lista de usuarios sin las identificaciones sea particularmente útil, por lo que hice que sea necesario aquí).

Si realmente quiere que sean el mismo tipo de objeto, entonces tal vez quiera modelar a un usuario con propiedades centrales que el servidor siempre envía, y un campo de "detalles" que podría ser nulo:

class User: Decodable { let id: Int let name: String let details: Details? struct Details: Decodable { var email: String var profile: String? var motive: String? var address: String? var profilePhotoUrl: String? } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) name = try container.decode(String.self, forKey: .name) details = container.contains(.email) ? try Details(from: decoder) : nil } enum CodingKeys: String, CodingKey { case id case name case email // Used to detect presence of Details } }

Tenga en cuenta que creo los Details , si están presentes, utilizando Details(from: decoder) , en lugar del container.decode(Details.self, forKey: .details) habitual container.decode(Details.self, forKey: .details) . Lo hago usando Details(from: decoder) para que las propiedades de los Details salgan del mismo objeto JSON que las propiedades del User , en lugar de requerir un objeto anidado.


Por lo general, hago que todas las propiedades no críticas sean opcionales, y luego tengo un inicializador que se puede obtener. Esto me permite manejar mejor cualquier cambio en el formato JSON o contratos de API rotos.

Por ejemplo:

class User { let id: Int let email: String var profile: String? var name: String? var motive: String? var address: String? var profilePhotoUrl: String? }

Esto significa que nunca tendré un objeto de usuario sin un ID o correo electrónico (supongamos que esos son los dos que siempre deben estar asociados con un usuario). Si obtengo una carga JSON sin un ID o correo electrónico, el Inicializador en la clase de Usuario fallará y no creará el objeto de usuario. Entonces tengo manejo de errores para inicializadores fallidos.

Preferiría tener una clase rápida con propiedades opcionales que un grupo de propiedades con un valor de cadena vacío.


Preferiría propiedades opcionales porque no puede prometer que los valores JSON estén allí todo el tiempo y cualquier cambio en el nombre de la propiedad de respuesta colapsaría su aplicación.

Si no usa valores opcionales, debe controlar los parámetros mientras analiza y agregar un valor predeterminado si desea una aplicación sin fallos. Y no sabría si era una cadena nula o vacía desde el servidor.

Los valores opcionales son tus mejores amigos.

Mapeador de objetos para propiedades mutables y no mutables.

realm-swift para valores predeterminados no opcionales.


Preferiría usar el Initializer de Failable en lugar de otras opciones.

Por lo tanto, mantenga las propiedades requeridas como no opcionales y cree un objeto solo si están presentes en la respuesta (puede usar if-let o gaurd-let para verificar esto en respuesta), de lo contrario no podrá crear el objeto.

Usando este enfoque, evitamos hacer opciones no opcionales y tener el dolor de manejarlas durante todo el programa.

Además, los opcionales no están destinados a la programación defensiva, por lo que no debe abusar de los opcionales haciendo que las propiedades "no opcionales" sean opcionales.


Recomiendo mantener todas non-scalar properties como opcionales, scalar como no opcionales (con algunas excepciones) asignando un valor predeterminado y colecciones con una matriz vacía. p.ej,

class User { var id: Int = 0 var name: String? var friends: [User] = [] }

Esto le asegura una aplicación sin fallos sin importar lo que pase con el servidor. Preferiría un comportamiento anormal en un choque.


Sí, debería usar opcional si la propiedad no es necesaria en la API y si desea algún valor en la propiedad obligatoria y luego asigne un valor en blanco:

class User { let id: Int? let email: String? = "" let profile: String? let name: String? = "" let motive: String? let address: String? let profilePhotoUrl: String? }


Si el servidor otorga un valor nulo para las otras propiedades, puede optar por opciones y desenvolver de forma segura. O mientras desenvuelve, puede asignar una cadena vacía a la propiedad si el valor es nulo

profile = jsonValue ?? ""

Otro caso, ya que las otras propiedades son tipo de datos de cadena, puede asignar un valor predeterminado como una cadena vacía

class User { let id: Int let email: String let profile: String = "" let name: String let motive: String = "" let address: String = "" let profilePhotoUrl: String = "" }