web-dev-qa-db-fra.com

Quelle est la meilleure façon de gérer la gestion des versions à l'aide du protocole JSON?

J'écris normalement toutes les parties du code en C # et lors de l'écriture de protocoles sérialisés, j'utilise FastSerializer qui sérialise/désérialise les classes rapidement et efficacement. Il est également très facile à utiliser et assez simple pour faire du "versioning", c'est-à-dire pour gérer différentes versions de la sérialisation. La chose que j'utilise normalement ressemble à ceci:

public override void DeserializeOwnedData(SerializationReader reader, object context)
{
    base.DeserializeOwnedData(reader, context);
    byte serializeVersion = reader.ReadByte(); // used to keep what version we are using

    this.CustomerNumber = reader.ReadString();
    this.HomeAddress = reader.ReadString();
    this.ZipCode = reader.ReadString();
    this.HomeCity = reader.ReadString();
    if (serializeVersion > 0)
        this.HomeAddressObj = reader.ReadUInt32();
    if (serializeVersion > 1)
        this.County = reader.ReadString();
    if (serializeVersion > 2)
        this.Muni = reader.ReadString();
    if (serializeVersion > 3)
        this._AvailableCustomers = reader.ReadList<uint>();
}

et

public override void SerializeOwnedData(SerializationWriter writer, object context)
{            
    base.SerializeOwnedData(writer, context);
    byte serializeVersion = 4; 
    writer.Write(serializeVersion);


    writer.Write(CustomerNumber);
    writer.Write(PopulationRegistryNumber);            
    writer.Write(HomeAddress);
    writer.Write(ZipCode);
    writer.Write(HomeCity);
    if (CustomerCards == null)
        CustomerCards = new List<uint>();            
    writer.Write(CustomerCards);
    writer.Write(HomeAddressObj);

    writer.Write(County);

    // v 2
    writer.Write(Muni);

    // v 4
    if (_AvailableCustomers == null)
        _AvailableCustomers = new List<uint>();
    writer.Write(_AvailableCustomers);
}

Il est donc facile d'ajouter de nouvelles choses ou de changer complètement la sérialisation si on le souhaite.

Cependant, je veux maintenant utiliser JSON pour des raisons non pertinentes ici =) J'utilise actuellement DataContractJsonSerializer et je cherche maintenant un moyen d'avoir la même flexibilité que j'ai en utilisant le FastSerializer ci-dessus.

La question est donc: quelle est la meilleure façon de créer un protocole/sérialisation JSON et de pouvoir détailler la sérialisation comme ci-dessus, afin de ne pas interrompre la sérialisation juste parce qu'une autre machine n'a pas encore mis à jour sa version?

45
Ted

La clé du contrôle de version JSON est de toujours ajouter de nouvelles propriétés et de ne jamais supprimer ou renommer les propriétés existantes. Ceci est similaire à comment les tampons de protocole gèrent la gestion des versions .

Par exemple, si vous avez commencé avec le JSON suivant:

{
  "version": "1.0",
  "foo": true
}

Et vous voulez renommer la propriété "foo" en "bar", ne vous contentez pas de la renommer. Ajoutez plutôt une nouvelle propriété:

{
  "version": "1.1",
  "foo": true,
  "bar": true
}

Comme vous ne supprimez jamais de propriétés, les clients basés sur des versions plus anciennes continueront de fonctionner. L'inconvénient de cette méthode est que le JSON peut se gonfler au fil du temps, et vous devez continuer à conserver les anciennes propriétés.

Il est également important de définir clairement vos cas "Edge" pour vos clients. Supposons que vous ayez une propriété de tableau appelée "fooList". La propriété "fooList" peut prendre les valeurs possibles suivantes: n'existe pas/non défini (la propriété n'est pas physiquement présente dans l'objet JSON, ou elle existe et est définie sur "non définie"), null, liste vide ou une liste avec une ou plusieurs valeurs. Il est important que les clients comprennent comment se comporter, en particulier dans les cas non définis/nuls/vides.

Je recommanderais également de lire comment versioning sémantique fonctionne. Si vous introduisez un schéma de version sémantique dans vos numéros de version, des modifications rétrocompatibles peuvent être apportées sur une limite de version mineure, tandis que des modifications de rupture peuvent être apportées sur une limite de version majeure (les clients et les serveurs devraient s'entendre sur la même version principale ). Bien que ce ne soit pas une propriété du JSON lui-même, cela est utile pour communiquer les types de modifications qu'un client doit attendre lorsque la version change.

33
monsur

Google Java based gson library a un excellent support de versioning pour json. Cela pourrait s'avérer très pratique si vous envisagez d'aller Java façon.

Il y a un tutoriel agréable et facile ici .

16
shashankaholic

N'utilisez pas DataContractJsonSerializer, comme son nom l'indique, les objets traités par cette classe devront:

a) Être marqué avec les attributs [DataContract] et [DataMember].

b) Être strictement conforme au "contrat" défini, c'est-à-dire ni plus ni moins que ce qu'il est défini. Tout [DataMember] supplémentaire ou manquant entraînera la désérialisation pour lever une exception.

Si vous voulez être assez flexible, utilisez JavaScriptSerializer si vous voulez opter pour l'option bon marché ... ou utilisez cette bibliothèque:

http://json.codeplex.com/

Cela vous donnera suffisamment de contrôle sur votre sérialisation JSON.

Imaginez que vous avez un objet à ses débuts.

public class Customer
{ 
    public string Name;

    public string LastName;
}

Une fois sérialisé, il ressemblera à ceci:

{Nom: "John", Nom: "Doe"}

Si vous modifiez la définition de votre objet pour ajouter/supprimer des champs. La désérialisation se fera en douceur si vous utilisez, par exemple, JavaScriptSerializer.

public class Customer
{ 
    public string Name;

    public string LastName;

    public int Age;
}

Si vous essayez de désérialiser le dernier json vers cette nouvelle classe, aucune erreur ne sera levée. Le fait est que vos nouveaux champs seront définis sur leurs valeurs par défaut. Dans cet exemple: "Age" sera mis à zéro.

Vous pouvez inclure, dans vos propres conventions, un champ présent dans tous vos objets, qui contient le numéro de version. Dans ce cas, vous pouvez faire la différence entre un champ vide ou une incohérence de version.

Disons donc: Vous avez votre classe Customer v1 sérialisée:

{ Version: 1, LastName: "Doe", Name: "John" }

Vous souhaitez désérialiser en une instance Customer v2, vous aurez:

{ Version: 1, LastName: "Doe", Name: "John", Age: 0}

Vous pouvez en quelque sorte, détecter quels champs de votre objet sont en quelque sorte fiables et ce qui ne l'est pas. Dans ce cas, vous savez que votre instance d'objet v2 provient d'une instance d'objet v1, le champ Age ne doit donc pas être approuvé.

Je pense que vous devez également utiliser un attribut personnalisé, par exemple "MinVersion", et marquez chaque champ avec le numéro de version minimum pris en charge, de sorte que vous obtenez quelque chose comme ceci:

public class Customer
{ 
    [MinVersion(1)]
    public int Version;

    [MinVersion(1)]
    public string Name;

    [MinVersion(1)]
    public string LastName;

    [MinVersion(2)]
    public int Age;
}

Plus tard, vous pourrez accéder à ces métadonnées et faire tout ce dont vous pourriez avoir besoin.

7
Adrian Salazar

Peu importe le protocole de sérialisation que vous utilisez, les techniques de version des API sont généralement les mêmes.

En général, vous avez besoin de:

  1. un moyen pour le consommateur de communiquer au producteur la version d'API qu'il accepte (bien que ce ne soit pas toujours possible)
  2. un moyen pour le producteur d'incorporer des informations de version dans les données sérialisées
  3. une stratégie rétrocompatible pour gérer les champs inconnus

Dans une API Web, généralement la version d'API acceptée par le consommateur est intégrée dans l'en-tête Accept (par exemple Accept: application/vnd.myapp-v1+json application/vnd.myapp-v2+json signifie que le consommateur peut gérer la version 1 et la version 2 de votre API) ou moins souvent dans l'URL (par exemple, https://api.Twitter.com/1/statuses/user_timeline.json). Ceci est généralement utilisé pour les versions majeures (c'est-à-dire les modifications incompatibles en arrière). Si le serveur et le client n'ont pas d'en-tête Accept correspondant, la communication échoue (ou procède au mieux ou se substitue à un protocole de base par défaut, selon la nature de l'application).

Le producteur génère ensuite des données sérialisées dans l'une des versions demandées, puis intègre ces informations de version dans les données sérialisées (par exemple sous la forme d'un champ nommé version). Le consommateur doit utiliser les informations de version incorporées dans les données pour déterminer comment analyser les données sérialisées. Les informations de version dans les données doivent également contenir une version mineure (c'est-à-dire pour les modifications rétrocompatibles), généralement les consommateurs doivent être en mesure d'ignorer les informations de version mineure et de traiter les données correctement, bien que la compréhension de la version mineure puisse permettre au client de faire des hypothèses supplémentaires sur comment les données doivent être traitées.

Une stratégie courante pour gérer les champs inconnus est comme la façon dont HTML et CSS sont analysés. Lorsque le consommateur voit un champ inconnu, il doit l'ignorer et lorsque les données manquent dans un champ attendu par le client, il doit utiliser une valeur par défaut; selon la nature de la communication, vous pouvez également spécifier certains champs obligatoires (c'est-à-dire que les champs manquants sont considérés comme une erreur fatale). Les champs ajoutés dans des versions mineures doivent toujours être des champs facultatifs; la version mineure peut ajouter des champs facultatifs ou modifier la sémantique des champs tant qu'elle est rétrocompatible, tandis que la version principale peut supprimer des champs ou ajouter des champs obligatoires ou modifier la sémantique des champs d'une manière incompatible en amont.

Dans un format de sérialisation extensible (comme JSON ou XML), les données doivent être auto-descriptives, en d'autres termes, les noms de champ doivent toujours être stockés avec les données; vous ne devez pas vous fier aux données spécifiques disponibles pour des postes spécifiques.

5
Lie Ryan