Use una cadena vacía, anule o elimine la propiedad vacía en la solicitud / respuesta API

25

Al transferir objetos a través de una API, como en el formato JSON sin esquema, ¿cuál es la forma ideal de devolver una propiedad de cadena inexistente? Sé que hay diferentes maneras de hacer esto, como en los ejemplos en los enlaces que figuran a continuación.

Estoy seguro de que he usado nulo en el pasado, pero no tengo una buena razón para hacerlo. Parece sencillo usar nulo cuando se trata con la base de datos. Pero la base de datos parece un detalle de implementación que no debería afectar a la parte del otro lado de la API. Por ejemplo, probablemente usan un almacén de datos sin esquema que solo almacena propiedades con valores (no nulos).

Desde el punto de vista del código, restringir las funciones de cadena para que funcionen solo con un tipo, es decir string(no nulo), las hace más fáciles de probar; evitar nulo es también una razón para tener Optionobjeto. Entonces, si el código que produce la solicitud / respuesta no usa nulo, supongo que el código en el otro lado de la API no se verá obligado a usar nulo también.

Prefiero la idea de usar una cadena vacía como una manera fácil de evitar usar nulo. Un argumento que escuché para usar nulo y contra la cadena vacía es que la cadena vacía significa que la propiedad existe. Aunque entiendo la diferencia, también me pregunto si es solo el detalle de implementación y si usar una cadena nula o vacía hace alguna diferencia en la vida real. También me pregunto si una cadena vacía es análoga a una matriz vacía.

Entonces, ¿cuál es la mejor manera de hacerlo que aborde esas preocupaciones? ¿Depende del formato del objeto que se transfiere (esquema / sin esquema)?

imel96
fuente
2
También tenga en cuenta que Oracle trata las cadenas vacías y las cadenas nulas de la misma manera. Y justo ahí están: en un cuestionario en papel, ¿cómo podría distinguir entre no dar respuesta y una respuesta que consiste en una cadena vacía?
Bernhard Hiller
Si usa la herencia, es fácil decir if( value === null) { use parent value; } Sin embargo, si establece el valor secundario, incluso en una cadena vacía (por ejemplo, anula el valor primario predeterminado con un espacio en blanco), entonces, ¿cómo "re-hereda" el valor? Para mí, establecerlo como nulo significaría "desarmar este valor para que sepamos usar el valor principal".
Frank Forte
Dado que "Eliminar propiedad vacía" también es la razón por la cual "evitar" a null(es cierto que nullse evita como tal), el interlocutor significa "Devolver no nulo" [Objeto] (es decir: cadena vacía, matriz vacía, etc.) cuando escribe "evitar".
cellepo

Respuestas:

18

TLDR; Eliminar propiedades nulas

Lo primero a tener en cuenta es que las aplicaciones en sus bordes no están orientadas a objetos (ni funcionales si se programa en ese paradigma). El JSON que recibe no es un objeto y no debe tratarse como tal. Son solo datos estructurados que pueden (o no) convertirse en un objeto. En general, no se debe confiar en ningún JSON entrante como objeto comercial hasta que se valide como tal. El solo hecho de que se deserialice no lo hace válido. Dado que JSON también tiene primitivas limitadas en comparación con los lenguajes de fondo, a menudo vale la pena hacer un DTO alineado con JSON para los datos entrantes. Luego, use el DTO para construir un objeto comercial (o un intento de error) para ejecutar la operación API.

Cuando mira JSON como solo un formato de transmisión, tiene más sentido omitir propiedades que no están establecidas. Es menos para enviar a través del cable. Si su idioma de back-end no utiliza valores nulos de forma predeterminada, probablemente podría configurar su deserializador para dar un error. Por ejemplo, mi configuración común para Newtonsoft.Json traduce propiedades nulas / faltantes solo a / desde optiontipos F # y, de lo contrario, generará un error. Esto proporciona una representación natural de qué campos son opcionales (aquellos con optiontipo).

Como siempre, las generalizaciones solo te llevan tan lejos. Probablemente hay casos en los que una propiedad nula o predeterminada se ajusta mejor. Pero la clave no es mirar las estructuras de datos en el borde de su sistema como objetos comerciales. Los objetos comerciales deben llevar garantías comerciales (por ejemplo, nombrar al menos 3 caracteres) cuando se crean correctamente. Pero las estructuras de datos extraídas no tienen garantías reales.

Kasey Speakman
fuente
3
Si bien la mayoría de los serializadores modernos tienen campos opcionales, omitir los valores nulos de la respuesta no siempre es una buena idea, ya que puede introducir una complejidad adicional para manejar campos anulables. Por lo tanto, depende realmente de las mayúsculas y minúsculas , dependiendo de cómo su biblioteca de serialización maneje valores anulables, y si la (adicional) complejidad adicional de manejar esos valores anulables realmente vale la pena guardar algunos bytes por solicitud. Debe trabajar duro para analizar sus casos de negocios.
Chris Cirefice
@ChrisCirefice Sí, creo que el último párrafo cubre eso. Hay casos en los que será mejor emplear diferentes estrategias.
Kasey Speakman
Estoy de acuerdo en que JSON se usa solo como formato de transmisión, no está pasando objetos a través de los cables como CORBA. También estoy de acuerdo en que las propiedades se pueden agregar y eliminar; las representaciones pueden cambiar, los controles pueden cambiar, especialmente en la web.
imel96
15

Actualización: he editado un poco la respuesta, porque puede haber generado confusión.


Ir con una cadena vacía es un no definitivo. La cadena vacía sigue siendo un valor, solo está vacía. No valor debe ser indicado el uso de una construcción que no representa nada, null.

Desde el punto de vista del desarrollador de API, solo existen dos tipos de propiedades:

  • requerido (estos DEBEN tener un valor de su tipo específico y NO DEBEN estar nunca vacíos),
  • opcional (PUEDEN contener un valor de su tipo específico pero PUEDEN contener también null.

Esto deja bastante claro que cuando una propiedad es obligatoria, es decir. requerido, nunca puede ser null.

Por otro lado, si una propiedad opcional de un objeto no se establece y se deja vacía, prefiero mantenerlos en la respuesta de todos modos con el nullvalor. Desde mi experiencia, hace que sea más fácil para los clientes API implementar el análisis, ya que no están obligados a verificar si una propiedad realmente existe o no, porque siempre está ahí, y simplemente pueden convertir la respuesta a su DTO personalizado, tratando los nullvalores como opcional

Incluir / eliminar dinámicamente campos de las fuerzas de respuesta, incluidas condiciones adicionales en los clientes.


De cualquier manera, de cualquier forma que elija, asegúrese de mantenerlo consistente y bien documentado. De esa manera, realmente no importa lo que use para su API, siempre que el comportamiento sea predecible.

Andy
fuente
Sí, la cadena vacía es un valor y lo uso nullpara referencias. Mezclar valores con referencias es una de mis preocupaciones. ¿Cómo diferencia los campos opcionales de los campos nullde cadena no opcionales que tienen nullvalor? Volviendo a analizar, ¿no probar la existencia de propiedades hace que el analizador sea más frágil?
imel96
2
@ imel96 Los campos no opcionales NUNCA pueden ser nulos. Si algo no es opcional, DEBE contener siempre un valor (de su tipo específico).
Andy
3
Esta. Como consumidor frecuente de API, odio cuando tengo que lidiar con estructuras "dinámicas" que vuelven a mí, incluso si se trata de la omisión de un campo opcional. (también muy de acuerdo en que hay una gran diferencia entre un ZLS y un Nulo). Felizmente aceptaría valores nulos todo el día. Como autor de API, uno de mis objetivos es hacer que el consumo del cliente sea lo más sencillo posible, y eso significa tener estructuras de datos esperadas, siempre.
jleach
@DavidPacker, entonces, si entiendo correctamente, usted usa nullpara indicar que el valor es opcional. Entonces, cuando define un objeto que tiene una propiedad de cadena no opcional, y el consumidor no tiene esta propiedad, debe enviar una cadena vacía para esa propiedad. ¿Está bien?
imel96
2
@ GregoryNisbet No hagas eso, por favor. Eso no tiene sentido.
Andy
3

null El uso depende de la aplicación / idioma

En última instancia, la elección de usar o no nullun valor de aplicación válido depende en gran medida de su aplicación y lenguaje de programación / interfaz / edge.

En un nivel fundamental, recomendaría tratar de usar tipos distintos si hay clases distintas de valores. nullpuede ser una opción si su interfaz lo permite y solo hay dos clases de una propiedad que está tratando de representar. Omitir una propiedad puede ser una opción si su interfaz o formato lo permite. Un nuevo tipo agregado (clase, objeto, tipo de mensaje) puede ser otra opción.

Para su ejemplo de cadena, si esto está en el lenguaje de programación, me haría un par de preguntas.

  1. ¿Planeo agregar futuros tipos de valores? Si es así, Optionprobablemente será mejor para el diseño de su interfaz.
  2. ¿Cuándo necesito validar las llamadas de los consumidores? ¿Inactivamente? ¿Dinamicamente? ¿Antes de? ¿Después? ¿En absoluto? Si su lenguaje de programación lo admite, use los beneficios de la escritura estática, ya que evita la cantidad de código que debe crear para la validación. Optionprobablemente llene este caso mejor si su cadena no es anulable. Sin embargo, es probable que tenga que verificar la entrada del usuario para un nullvalor de cadena de todos modos, por lo que probablemente diferiré de nuevo a la primera línea de preguntas: cuántos tipos de valores quiero / quiero representar.
  3. ¿Es nullindicativo de un error del programador en mi lenguaje de programación? Desafortunadamente, a nullmenudo es el valor predeterminado para punteros o referencias no inicializados (o no inicializados explícitamente) en algunos idiomas. ¿Es nullun valor aceptable como valor predeterminado? ¿Es seguro como valor predeterminado? A veces nulles indicativo de valores desasignados. ¿Debo proporcionar a los consumidores de mi interfaz una indicación de estos posibles problemas de administración de memoria o inicialización en su programa? ¿Cuál es el modo de falla de tal llamada ante tales problemas? ¿La persona que llama está en el mismo proceso o hilo que el mío, de modo que tales errores son un riesgo alto para mi aplicación?

Dependiendo de sus respuestas a estas preguntas, probablemente podrá centrarse en si es o no nulladecuado para su interfaz.

Ejemplo 1

  1. Su aplicación es crítica para la seguridad.
  2. Está utilizando algún tipo de inicialización de montón en el inicio y nulles un posible valor de cadena que se devuelve al no poder asignar espacio para una cadena.
  3. Existe la posibilidad de que una cadena llegue a su interfaz

Respuesta: nullprobablemente no sea apropiado

Justificación: nullen este caso, en realidad se usa para indicar dos tipos diferentes de valores. El primero puede ser un valor predeterminado que el usuario de su interfaz puede establecer. Desafortunadamente, el segundo valor es un indicador para indicar que su sistema no funciona correctamente. En tales casos, probablemente desee fallar de la manera más segura posible (lo que sea que eso signifique para su sistema).

Ejemplo 2

  1. Estás utilizando una estructura C que tiene un char *miembro.
  2. Su sistema no utiliza la asignación de almacenamiento dinámico y está utilizando la verificación MISRA.
  3. Su interfaz acepta esta estructura como un puntero y verifica para asegurarse de que la estructura no apunta a NULL
  4. El valor predeterminado y seguro del char *miembro para su API se puede indicar mediante un solo valor deNULL
  5. Tras la inicialización de la estructura de su usuario, le gustaría brindarle al usuario la posibilidad de no inicializar explícitamente al char *miembro.

Respuesta: NULLpuede ser apropiado

Justificación: existe una pequeña posibilidad de que su estructura pase la NULLverificación pero no se haya inicializado. Sin embargo, es posible que su API no pueda dar cuenta de esto a menos que tenga algún tipo de suma de verificación en el valor de estructura y / o la comprobación de rango de la dirección de la estructura. Las listas MISRA-C pueden ayudar a los usuarios de su API marcando el uso de estructuras antes de su inicialización. Sin embargo, en cuanto al char *miembro, si el puntero a estructura apunta a una estructura inicializada, NULLes el valor predeterminado de un miembro no especificado en un inicializador de estructura. Por lo tanto, NULLpuede servir como un valor predeterminado seguro para el char *miembro de estructura en su aplicación.

Si está en una interfaz de serialización, me haría las siguientes preguntas sobre si usar o no nulo en una cadena.

  1. ¿Es nullindicativo de un posible error del lado del cliente? Para JSON en JavaScript, este es probablemente un no, ya nullque no se usa necesariamente como una indicación de falla de asignación. En JavaScript, se utiliza como una indicación explícita de la ausencia de objetos de una referencia que se establece de forma problemática. Sin embargo, existen analizadores y serializadores que no son JavaScript que asignan JSON nullal nulltipo nativo . Si este es el caso, se inicia la discusión sobre si el nulluso nativo es adecuado para su combinación particular de idioma, analizador y serializador.
  2. ¿La ausencia explícita de un valor de propiedad impacta más que un solo valor de propiedad? A veces, a en nullrealidad indica que tiene un nuevo tipo de mensaje por completo. Puede ser más limpio para sus consumidores el formato de serialización simplemente especificar un tipo de mensaje completamente diferente. Esto garantiza que su validación y lógica de aplicación puedan tener una separación clara entre las dos distinciones de mensajes que proporciona su interfaz web.

Consejo general

nullno puede ser un valor en un borde o interfaz que no lo admite. Si está utilizando algo que es extremadamente suelto en la tipificación de valores de propiedades (es decir, JSON), intente introducir alguna forma de esquema o validación en el software de borde de los consumidores (por ejemplo, Esquema JSON ) si puede. Si se trata de una API de lenguaje de programación, valide la entrada del usuario de forma estática si es posible (a través de la escritura) o tan fuerte como sea razonable en tiempo de ejecución (también conocido como practicar la programación defensiva en interfaces orientadas al consumidor). Lo que es más importante, documente o defina el borde para que no haya dudas en cuanto a:

  • Qué tipo (s) de valor acepta una propiedad determinada
  • Qué rangos de valor son válidos para una propiedad determinada.
  • Cómo se debe estructurar un tipo agregado. ¿Qué propiedades deben / deberían / ​​pueden estar presentes en un tipo agregado?
  • Si se trata de algún tipo de contenedor, ¿cuántos elementos pueden o deben contener el contenedor y cuáles son los tipos de valores que contiene el contenedor?
  • ¿Qué orden, si corresponde, son las propiedades o instancias de un tipo de contenedor o agregado devuelto?
  • ¿Qué efectos secundarios existen al establecer valores particulares y cuáles son los efectos secundarios de leer esos valores?
retrodispersado
fuente
1

Aquí mi análisis personal de estas preguntas. No está respaldado por ningún libro, trabajo, estudio o lo que sea, solo mi experiencia personal.

Cadenas vacías como null

Esto es un no-go para mí. No mezcle la semántica de una cadena vacía con la de no definida. En muchos casos, pueden ser perfectamente intercambiables, pero puede encontrarse con casos en los que no definido y definido pero vacío significa algo diferente.

Un tipo de ejemplo estúpido: digamos que hay un atributo que almacena una clave foránea, y ese atributo no está definido o es null, eso significaría que no hay una relación definida, mientras que una cadena vacía ""podría entenderse como una relación definida y el La identificación del registro externo es esa cadena vacía.

No definido vs null

Este no es un tema blanco o negro. Hay ventajas y desventajas de ambos enfoques.

A favor de la definición explícita de nullvalores, existen estos pros:

  • Los mensajes son más descriptivos, ya que puedes conocer todas las claves con solo mirar cualquier mensaje.
  • En relación con el punto anterior, es más fácil codificar y detectar errores en el consumidor de los datos: es más fácil detectar errores si obtiene las claves incorrectas (tal vez la falta de ortografía, tal vez la API cambió, etc.).

A favor de asumir que una clave no existente es igual a la semántica de null:

  • Algunos cambios son más fáciles de acomodar. Por ejemplo, si una nueva versión del esquema del mensaje incluye una nueva clave, puede codificar al consumidor de la información para que trabaje con esta clave futura, incluso cuando el productor del mensaje no se haya actualizado y aún no entregue esta información.
  • Los mensajes pueden ser menos detallados o más cortos

En caso de que la API sea de alguna manera estable, y la documente a fondo, creo que está perfectamente bien decir que una clave inexistente equivale al significado de null. Pero si es más desordenado y caótico (como suele serlo), creo que puede evitar los dolores de cabeza si define explícitamente cada valor en cada mensaje. Es decir, si tengo dudas, tiendo a seguir el enfoque detallado.

Dicho todo esto, lo más importante: declara tus intenciones claramente y sé coherente. No hagas una cosa aquí y la otra allá. El software predecible es un mejor software.

bgusach
fuente
El ejemplo para usar cadenas vacías es lo que quiero decir con detalles de implementación, es decir, suponiendo que la API se usa para exponer filas de la base de datos. ¿Hará alguna diferencia si no hay una base de datos involucrada y solo para transferir representaciones de objetos?
imel96
No tiene que ser un detalle de implementación. Mi ejemplo en realidad habla de PK que están relacionadas con DB, pero lo que intenté explicar es que una cadena vacía no es nula / nada / null. Otro ejemplo: en un juego, hay un objeto de personaje y tiene un atributo "compañero". Un nullsocio significa claramente que no hay ningún socio, pero ""puede entenderse como un socio cuyo nombre es "".
bgusach
Estoy de acuerdo con la referencia de socio nulo significa que no hay socio y que la referencia no es una cadena. Pero el nombre de socio es una cadena, incluso si permite nulo como nombre de socio, ¿no lo captaría y lo reemplazaría con una cadena vacía en algún momento?
imel96
Si no hay pareja, no cambiaría la nullcadena vacía. Tal vez renderizándolo en un formulario, pero nunca en el modelo de datos.
bgusach
No me refería a ningún compañero, el compañero sería un objeto. Es el socio del nameque estaba hablando, ¿permitiría que el nombre del socio sea nulo?
imel96
1

Proporcionaría una cadena vacía en una situación en la que hay una cadena presente, y resulta ser una cadena vacía. Proporcionaría nulo en una situación en la que quiero decir explícitamente "no, estos datos no están allí". Y omita la clave para decir "no hay datos allí, no se moleste".

Usted juzga cuál de estas situaciones puede suceder. ¿Tiene sentido que su aplicación tenga cadenas vacías? ¿Desea distinguir entre decir explícitamente "sin datos" usando nulo e implícitamente sin valor? Solo debe tener ambas posibilidades (nula y sin clave presente) si el cliente necesita distinguir ambas.

Ahora tenga en cuenta que se trata de transmitir datos. Lo que hace el receptor con los datos es su negocio, y harán lo que les sea más conveniente. El receptor debe poder manejar todo lo que le arroje (posiblemente rechazando los datos) sin fallar.

Si no hay otras consideraciones, transmitiría lo que sea más conveniente para el remitente y lo documentaría . Prefiero no enviar valores ausentes porque es probable que esto mejore la velocidad de codificación, transmisión y análisis de JSON.

gnasher729
fuente
Me gusta su punto en "si necesita ser distinguido por el cliente".
imel96
0

Aunque no puedo decir qué es lo mejor , casi con certeza no es un simple detalle de implementación , cambia la estructura de cómo puede interactuar con esa variable.

Si algo puede ser nulo , siempre debe tratarlo como si fuera nulo en algún momento , por lo que siempre tendrá dos flujos de trabajo , uno para nulo y otro para una cadena válida. Un flujo de trabajo dividido no es necesariamente algo malo, ya que hay bastante manejo de errores y usos de casos especiales que podría utilizar, pero ofusca su código.

Si siempre interactúa con la cadena de la misma manera , probablemente será más fácil que la funcionalidad permanezca en su cabeza .

Entonces, como con cualquier pregunta de "qué es lo mejor", me queda la respuesta: depende . Si desea dividir su flujo de trabajo y capturar más explícitamente cuando algo no está configurado, use nulo. Si prefiere que el programa siga haciendo lo que hace, use una cadena vacía. Lo importante es que usted sea consistente , elija un rendimiento común y siga con eso.

Teniendo en cuenta que está creando una API, le recomendaría que se adhiera a una cadena vacía, ya que hay menos para que el usuario compense, porque como usuario de una API no sabré todas las razones por las que su API podría darme un valor nulo a menos que usted ' están muy bien documentados, lo que algunos usuarios no leerán de todos modos.

Erdrik Ironrose
fuente
Tener "flujo de trabajo dividido" es malo. Digamos que en el lado del productor todo está limpio, los métodos de tipo cadena solo devuelven strings, nunca son nulos. Si la API usa nulo, en algún momento el productor necesita crear esto nullpara cumplir con la API. Entonces el consumidor necesita manejar nulltambién. Pero creo que entendí lo que estás diciendo, solo decide y define la API con autoridad, ¿verdad? ¿Eso significa que no hay nada malo con ninguno de ellos?
imel96
Sí, cualquier cosa que haga en su API afectará la forma en que el usuario tendrá que estructurar su código, por lo tanto, teniendo en cuenta su diseño en términos del usuario de la API, debería poder definir cuál es la mejor manera. En definitiva es tu API. Solo sé consistente. Solo usted puede decidir los pros y los contras del enfoque.
Erdrik Ironrose
0

¡Documento!

TL; DR>

Haz lo que te parezca conveniente: a veces el contexto en el que se usa es importante. Ejemplo, enlazar variables a un Oracle SQL: la cadena vacía se interpreta como NULL.

Simplemente diría: asegúrese de documentar cada escenario mencionado

  • NULO
  • En blanco (vacío)
  • Falta (eliminado)

Su código puede actuar de diferentes maneras: documente cómo reacciona su código:

  • Falla (excepción, etc.), tal vez incluso falla la validación (posiblemente una excepción comprobada) vs no se puede manejar la situación correctamente (NullPointerException).
  • Proporcionar valores predeterminados razonables
  • El código se comporta de manera diferente

Luego, además de eso, depende de usted comportarse de manera consistente y posiblemente adoptar algunas de sus mejores prácticas. Documentar ese comportamiento consistente. Ejemplos:

  • Tratar a nulo y faltar igual
  • Trate una cadena vacía exactamente como tal. Solo en el caso de un enlace SQL, podría considerarse un espacio en blanco. Asegúrese de que su SQL se comporte de manera consistente y esperada.
Yoyó
fuente
El problema es que, sin abordar las preocupaciones, el desacuerdo ocurrió con bastante frecuencia. Considere en el entorno del equipo, la decisión tiene que ser una decisión del equipo, muchas veces eso significa que habría una discusión. Cuando tienes varios equipos, cada equipo tiene derecho a sus propias decisiones. He visto API que solo puedo adivinar que son implementadas por diferentes equipos que no están de acuerdo entre sí. Si alguien puede aceptar algo, documentarlo es trivial.
imel96
0

tl; dr - si lo usa: sea consistente en lo que significa.

Si lo incluyeras null, ¿qué significaría? Hay un universo de cosas que podría significar. Un valor simplemente no es suficiente para representar un valor perdido o desconocido (y estas son solo dos de las innumerables posibilidades: por ejemplo, Desaparecido - se midió, pero aún no lo sabemos. Desconocido - no intentamos medir eso.)

En un ejemplo que encontré recientemente, un campo podría estar vacío porque no se informó que protegiera la privacidad de alguien, pero se conocía del lado del remitente, no se conocía del lado del remitente, pero se lo conoce al reportero original o se desconoce para ambos. Y todo esto le importaba al receptor. Por lo general, un valor no es suficiente.

Con una suposición de mundo abierto (simplemente no sabes acerca de las cosas no mencionadas), simplemente lo dejarías fuera y podría ser cualquier cosa. Con la suposición de mundo cerrado (las cosas que no se mencionan son falsas, por ejemplo en SQL) es mejor dejar claro qué nullsignifica y ser lo más coherente posible con esa definición ...

Grimaldi
fuente