web-dev-qa-db-fra.com

Utilisez une chaîne vide, null ou supprimez une propriété vide dans la demande / réponse de l'API

Lors du transfert d'un objet via une API, comme au format JSON sans schéma, quel est le moyen idéal pour renvoyer une propriété de chaîne inexistante? Je sais qu'il existe différentes façons de le faire, comme dans les exemples des liens répertoriés ci-dessous.

Je suis sûr que j'ai utilisé null dans le passé, mais je n'ai pas de bonnes raisons de le faire. Il semble simple d'utiliser null lorsqu'il s'agit de la base de données. Mais la base de données semble être un détail d'implémentation qui ne devrait pas concerner la partie de l'autre côté de l'API. Par exemple. ils utilisent probablement une banque de données sans schéma qui stocke uniquement les propriétés avec des valeurs (non nulles).

Du point de vue du code, restreindre les fonctions de chaîne pour qu'elles ne fonctionnent qu'avec un seul type, c'est-à-dire string (non nul), les rend plus faciles à prouver; éviter null est aussi une raison pour avoir Option objet. Donc, si le code qui produit la demande/réponse n'utilise pas null, je suppose que le code de l'autre côté de l'API ne sera pas forcé d'utiliser également null.

J'aime plutôt l'idée d'utiliser une chaîne vide comme moyen facile d'éviter d'utiliser null. Un argument que j'ai entendu pour utiliser null et contre la chaîne vide est que la chaîne vide signifie que la propriété existe. Bien que je comprenne la différence, je me demande également si ce n'est que le détail de l'implémentation et si l'utilisation d'une chaîne nulle ou vide fait une réelle différence. Je me demande également si une chaîne vide est analogue à un tableau vide.

Alors, quelle est la meilleure façon de le faire pour répondre à ces préoccupations? Cela dépend-il du format de l'objet transféré (schéma/sans schéma)?

25
imel96

TLDR; Supprimez les propriétés nulles

La première chose à garder à l'esprit est que les applications à leur les bords ne sont pas orientés objet (ni fonctionnelles si la programmation dans ce paradigme). Le JSON que vous recevez n'est pas un objet et ne doit pas être traité comme tel. Ce sont juste des données structurées qui peuvent (ou non) se convertir en objet. En général, aucun JSON entrant ne doit être approuvé en tant qu'objet métier tant qu'il n'est pas validé en tant que tel. Le fait qu'il soit désérialisé ne le rend pas valide. Puisque JSON a également des primitives limitées par rapport aux langages d'arrière-plan, cela vaut souvent la peine de le faire un alignement JSON DTO pour les données entrantes. Utilisez ensuite le DTO pour construire un objet métier (ou une erreur lors de la tentative) pour exécuter l'opération d'API.

Lorsque vous regardez JSON comme un simple format de transmission, il est plus logique d'omettre des propriétés qui ne sont pas définies. C'est moins d'envoyer à travers le fil. Si votre langue d'arrière-plan n'utilise pas de valeurs nulles par défaut, vous pouvez probablement configurer votre désérialiseur pour qu'il génère une erreur. Par exemple, ma configuration courante pour Newtonsoft.Json traduit uniquement les propriétés nulles/manquantes vers/depuis les types F # option et entraînera sinon une erreur. Cela donne une représentation naturelle des champs facultatifs (ceux de type option).

Comme toujours, les généralisations ne vous mènent que jusqu'à présent. Il y a probablement des cas où une propriété par défaut ou nulle correspond mieux. Mais la clé n'est pas de considérer les structures de données à la périphérie de votre système comme des objets métier. Les objets métier doivent porter des garanties commerciales (par exemple, nommer au moins 3 caractères) lorsqu'ils sont créés avec succès. Mais les structures de données retirées du fil n'ont aucune réelle garantie.

21
Kasey Speakman

Mise à jour: J'ai modifié un peu la réponse, car cela peut avoir semé la confusion.


Aller avec une chaîne vide est un non définitif. Une chaîne vide est toujours une valeur, elle est juste vide. Aucune valeur ne doit être indiquée à l'aide d'une construction qui ne représente rien, null.

Du point de vue du développeur d'API, il n'existe que deux types de propriétés:

  • requis (ceux-ci DOIVENT avoir une valeur de leur type spécifique et NE DOIVENT JAMAIS être vides),
  • facultatif (ceux-ci PEUVENT contenir une valeur de leur type spécifique mais PEUVENT également contenir null.

Cela montre clairement que lorsqu'une propriété est obligatoire, c.-à-d. requis, il ne peut jamais être null.

D'un autre côté, si une propriété facultative d'un objet n'est pas définie et laissée vide, je préfère quand même les conserver dans la réponse avec la valeur null. D'après mon expérience, il est plus facile pour les clients API d'implémenter l'analyse, car ils ne sont pas tenus de vérifier si une propriété existe réellement ou non, car elle est toujours là, et ils peuvent simplement convertir la réponse en leur DTO personnalisé, en traitant null valeurs facultatives.

Inclure/supprimer dynamiquement des champs des forces de réponse, y compris des conditions supplémentaires sur les clients.


Quoi qu'il en soit, quelle que soit la manière que vous choisissez, assurez-vous de le garder cohérent et bien documenté. De cette façon, peu importe ce que vous utilisez pour votre API, tant que le comportement est prévisible.

15
Andy

null L'utilisation dépend de l'application/de la langue

En fin de compte, le choix d'utiliser ou non null comme valeur d'application valide est largement déterminé par votre application et votre langage de programmation/interface/Edge.

À un niveau fondamental, je recommanderais d'essayer d'utiliser des types distincts s'il existe des classes de valeurs distinctes. null peut être une option si votre interface le permet et qu'il n'y a que deux classes d'une propriété que vous essayez de représenter. L'omission d'une propriété peut être une option si votre interface ou votre format le permet. Un nouveau type d'agrégat (classe, objet, type de message) peut être une autre option.

Pour votre exemple de chaîne, si c'est dans le langage de programmation, je me poserais quelques questions.

  1. Ai-je l'intention d'ajouter de futurs types de valeurs? Si c'est le cas, un Option sera probablement meilleur pour la conception de votre interface.
  2. Quand dois-je valider les appels des consommateurs? Statiquement? Dynamiquement? Avant? Après? Du tout? Si votre langage de programmation le prend en charge, profitez des avantages de la frappe statique car il évite la quantité de code que vous devez créer pour la validation. Option remplit probablement ce cas mieux si votre chaîne n'était pas nullable. Cependant, vous devrez probablement vérifier l'entrée utilisateur pour une valeur de chaîne null de toute façon, donc je reviendrais probablement à la première ligne de questionnement: combien de types de valeurs veux/veux-je représenter.
  3. null indique-t-il une erreur de programmation dans mon langage de programmation? Malheureusement, null est souvent la valeur par défaut pour les pointeurs ou références non initialisés (ou initialisés de manière non explicite) dans certaines langues. null est-il une valeur acceptable comme valeur par défaut? Est-ce sûr comme valeur par défaut? Parfois, null indique des valeurs désallouées. Dois-je fournir aux consommateurs de mon interface une indication de ces problèmes potentiels de gestion de la mémoire ou d'initialisation dans leur programme? Quel est le mode d'échec d'un tel appel face à de tels problèmes? L'appelant est-il dans le même processus ou thread que le mien afin que de telles erreurs soient un risque élevé pour mon application?

En fonction de vos réponses à ces questions, vous pourrez probablement déterminer si null convient à votre interface.

Exemple 1

  1. Votre application est essentielle à la sécurité
  2. Vous utilisez un type d'initialisation de segment de mémoire au démarrage et null est une valeur de chaîne possible renvoyée en cas d'échec d'allocation d'espace pour une chaîne.
  3. Il y a un potentiel qu'une telle chaîne frappe votre interface

Réponse: null n'est probablement pas approprié

Justification: null dans ce cas est en fait utilisé pour indiquer deux types de valeurs différents. La première peut être une valeur par défaut que l'utilisateur de votre interface peut vouloir définir. Malheureusement, la deuxième valeur est un indicateur pour indiquer que votre système ne fonctionne pas correctement. Dans de tels cas, vous souhaiterez probablement échouer de la manière la plus sûre possible (quoi que cela signifie pour votre système).

Exemple 2

  1. Vous utilisez une structure C qui a un char * membre.
  2. Votre système n'utilise pas d'allocation de segment et vous utilisez la vérification MISRA.
  3. Votre interface accepte cette structure comme pointeur et vérifie que la structure ne pointe pas vers NULL
  4. La valeur par défaut et sûre de char * le membre de votre API peut être indiqué par une seule valeur de NULL
  5. Lors de l'initialisation de la structure de votre utilisateur, vous souhaitez donner à l'utilisateur la possibilité de ne pas initialiser explicitement le char * membre.

Réponse: NULL peut être approprié

Raisonnement: il y a un peu de chance que votre struct passe le contrôle NULL mais n'est pas initialisé. Cependant, votre API peut ne pas être en mesure de tenir compte de cela, sauf si vous avez une sorte de somme de contrôle sur la valeur de la structure et/ou la vérification de la plage de l'adresse de la structure. Les linters MISRA-C peuvent aider les utilisateurs de votre API en signalant l'utilisation des structures avant leur initialisation. Cependant, comme pour le char * membre, si le pointeur sur struct pointe vers une structure initialisée, NULL est la valeur par défaut d'un membre non spécifié dans un initialiseur de structure. Par conséquent, NULL peut servir de valeur par défaut sûre pour le char * membre struct dans votre application.

Si c'est sur une interface de sérialisation, je me poserais les questions suivantes sur l'utilisation ou non de null sur une chaîne.

  1. null indique-t-il une erreur potentielle côté client? Pour JSON en JavaScript, il s'agit probablement d'un non car null n'est pas nécessairement utilisé comme indication d'un échec d'allocation. En JavaScript, il est utilisé comme une indication explicite de l'absence d'objet d'une référence à définir de manière problématique. Cependant, il existe des analyseurs et des sérialiseurs non javascript qui mappent JSON null au type natif null. Si tel est le cas, cela amène à discuter de la question de savoir si l'utilisation native de null est correcte pour votre combinaison particulière de langue, analyseur et sérialiseur.
  2. L'absence explicite d'une valeur de propriété affecte-t-elle plus qu'une seule valeur de propriété? Parfois, un null indique en fait que vous avez un nouveau type de message entièrement. Il peut être plus simple pour vos consommateurs du format de sérialisation de simplement spécifier un type de message complètement différent. Cela garantit que leur validation et leur logique d'application peuvent avoir une séparation nette entre les deux distinctions de messages fournies par votre interface Web.

Conseils généraux

null ne peut pas être une valeur sur un Edge ou une interface qui ne le prend pas en charge. Si vous utilisez quelque chose qui est extrêmement lâche dans la nature de la saisie des valeurs des propriétés (c'est-à-dire JSON), essayez de pousser une certaine forme de schéma ou de validation sur le logiciel Edge du consommateur (par exemple schéma JSON ) si vous pouvez. S'il s'agit d'une API de langage de programmation, validez l'entrée utilisateur si possible statiquement (via la saisie) ou aussi fort que cela est sensé lors de l'exécution (pratique connue sous le nom programmation défensive sur les interfaces destinées aux consommateurs). Tout aussi important, documentez ou définissez l'Edge afin qu'il ne soit pas question de:

  • Quel (s) type (s) de valeur une propriété donnée accepte
  • Quelles plages de valeurs sont valides pour une propriété donnée.
  • Comment un type d'agrégat doit être structuré. Quelles propriétés doivent/devraient/peuvent être présentes dans un type d'agrégat?
  • S'il s'agit d'un type de conteneur, combien d'éléments le conteneur peut-il ou doit-il contenir, et quels sont les types de valeurs qu'il contient?
  • Dans quel ordre, le cas échéant, les propriétés ou instances d'un type de conteneur ou d'agrégats sont-elles renvoyées?
  • Quels sont les effets secondaires de la définition de valeurs particulières et quels sont les effets secondaires de la lecture de ces valeurs?
3
backscattered

Je fournirais une chaîne vide dans une situation où une chaîne est présente, et il se trouve que c'est une chaîne vide. Je fournirais null dans une situation où je voudrais dire explicitement "non, ces données ne sont pas là". Et omettez la clé pour dire "pas de données là-bas, ne vous embêtez pas".

Vous jugez laquelle de ces situations peut se produire. Est-il logique que votre application ait des chaînes vides? Voulez-vous faire la distinction entre dire explicitement "pas de données" en utilisant null et implicitement sans valeur? Vous ne devriez avoir les deux possibilités (null et aucune clé présente) si les deux doivent être distinguées par le client.

Gardez maintenant à l'esprit qu'il s'agit de transmettre des données. Ce que le destinataire fait des données, c'est son affaire, et il fera ce qui lui convient le mieux. Le récepteur devrait être capable de gérer tout ce que vous lui lancez (éventuellement en rejetant les données) sans planter.

S'il n'y a pas d'autres considérations, je transmettrais ce qui est le plus pratique pour l'expéditeur et document. Je préfère ne pas envoyer de valeurs absentes du tout, car cela améliorera probablement la vitesse d'encodage, de transmission et d'analyse JSON.

1
gnasher729

Voici mon analyse personnelle de ces questions. Il n'est soutenu par aucun livre, papier, étude ou autre, juste mon expérience personnelle.

Chaînes vides comme null

C'est un pas pour moi. Ne mélangez pas la sémantique d'une chaîne vide avec celle d'une chaîne non définie. Dans de nombreux cas, ils peuvent être parfaitement interchangeables, mais vous pouvez rencontrer des cas où non définis et définis mais vides signifient quelque chose de différent.

Une sorte d'exemple stupide: disons qu'il y a un attribut qui stocke une clé étrangère, et que cet attribut n'est pas défini ou est null, cela voudrait dire qu'il n'y a pas de relation définie, alors qu'une chaîne vide "" pourrait être compris comme une relation définie et l'ID de l'enregistrement étranger est cette chaîne vide.

Non défini vs null

Ce n'est pas un sujet noir ou blanc. Les deux approches présentent des avantages et des inconvénients.

En faveur de la définition explicite des valeurs de null, il y a ces avantages:

  • Les messages sont plus auto-descriptifs en ce que vous apprenez à connaître toutes les clés simplement en regardant n'importe quel message
  • Par rapport au point précédent, il est plus facile de coder et de détecter les erreurs chez le consommateur des données: il est plus facile de détecter les erreurs si vous récupérez les mauvaises clés (peut-être une faute d'orthographe, peut-être que l'API a changé, etc.).

En faveur de l'hypothèse qu'une clé non existante est égale à la sémantique de null:

  • Certains changements sont plus faciles à adapter. Par exemple, si une nouvelle version du schéma de message comprend une nouvelle clé, vous pouvez coder le consommateur des informations pour travailler avec cette future clé, même lorsque le producteur du message n'a pas été mis à jour et ne fournit pas encore ces informations.
  • Les messages peuvent être moins verbeux ou plus courts

Dans le cas où l'API est en quelque sorte stable et que vous la documentez à fond, je pense qu'il est parfaitement correct de déclarer qu'une clé inexistante équivaut à la signification de null. Mais si c'est plus compliqué et chaotique (comme c'est souvent le cas), je pense que vous pouvez éviter les maux de tête si vous définissez explicitement chaque valeur dans chaque message. C'est à dire. en cas de doute, j'ai tendance à suivre l'approche verbeuse.

Cela dit, la chose la plus importante: énoncez clairement vos intentions et soyez cohérent. Ne faites pas une chose ici et l'autre là-bas. Un logiciel prévisible est un meilleur logiciel.

1
bgusach

Document!

TL; DR>

Faites comme bon vous semble - parfois le contexte dans lequel il est utilisé est important. Exemple, lier des variables à Oracle SQL: une chaîne vide est interprétée comme une valeur NULL.

Je dirais simplement - Assurez-vous de documenter chaque scénario mentionné

  • NUL
  • Vide (vide)
  • Manquant (supprimé)

Votre code peut agir de différentes manières: documentez la réaction de votre code:

  • Échec (exception, etc.), peut-être même échoue la validation (éventuellement une exception vérifiée) vs ne peut pas gérer la situation correctement (NullPointerException).
  • Fournir des valeurs par défaut raisonnables
  • Le code se comporte différemment

Ensuite, en plus de cela - c'est à vous de vous comporter de manière cohérente, et éventuellement d'adopter certaines de vos meilleures pratiques. Documentez ce comportement cohérent. Exemples:

  • Traitez Null et Missing de la même façon
  • Traitez une chaîne vide exactement comme telle. Uniquement dans le cas d'une liaison SQL, elle peut être considérée comme vide. Assurez-vous que votre SQL se comporte de manière cohérente et attendue.
0
YoYo

tl; dr - si vous l'utilisez: soyez cohérent dans ce que cela signifie.

Si vous incluez null, qu'est-ce que cela signifie? Il y a un univers de choses ce que cela pourrait signifier. Une valeur n'est tout simplement pas suffisante pour représenter une valeur manquante ou inconnue (et ce ne sont que deux de la myriade de possibilités: par exemple manquant - il a été mesuré, mais nous ne le savons pas encore. Inconnu - nous n'avons pas essayé de mesurer il.)

Dans un exemple que j'ai rencontré récemment, un champ peut être vide car il n'a pas été signalé pour protéger la vie privée de quelqu'un, mais il était connu du côté de l'expéditeur, inconnu du côté de l'expéditeur, mais connu du journaliste d'origine ou inconnu des deux. Et tout cela comptait pour le récepteur. Donc, généralement, une valeur ne suffit pas.

Avec une hypothèse de monde ouvert (vous ne savez tout simplement pas sur les choses non énoncées), vous laisseriez simplement de côté et cela pourrait être n'importe quoi. Avec l'hypothèse du monde fermé (les choses non énoncées sont fausses, par exemple en SQL), vous feriez mieux de préciser ce que null signifie et d'être aussi cohérent avec cette définition que possible ...

0
Grimaldi

Bien que je ne puisse pas dire ce qui est le meilleur c'est presque certainement pas un simple détail d'implémentation, cela change la structure de la façon dont vous pouvez interagir avec cette variable.

Si quelque chose peut être nul vous devez toujours le traiter comme s'il sera nul à un moment donné, donc vous aurez toujours deux workflows, un pour null, un pour une chaîne valide. Un flux de travail fractionné n'est pas nécessairement une mauvaise chose, car il existe un certain nombre de gestion d'erreurs et d'utilisations spéciales que vous pouvez utiliser, mais il obscurcit votre code.

Si vous interagissez toujours avec la chaîne de la même manière il sera probablement plus facile pour la fonctionnalité de restez dans votre tête.

Donc, comme pour toute question "ce qui est le mieux", il me reste la réponse: cela dépend. Si vous souhaitez diviser votre flux de travail et capturer plus explicitement lorsque quelque chose n'est pas défini, utilisez null. Si vous préférez que le programme continue comme il le fait, utilisez une chaîne vide. L'important est que vous soyez cohérent, choisissez un retour commun et respectez-le.

Étant donné que vous créez une API, je recommanderais coller à une chaîne vide car il y a moins à compenser pour l'utilisateur, car en tant qu'utilisateur d'une API, je ne connais pas toutes les raisons votre API pourrait me donner une valeur nulle sauf si vous êtes très bien documenté, ce que certains utilisateurs ne liront pas de toute façon.

0
Erdrik Ironrose