2012-04-06 7 views
40

Normalmente escribo todas las partes del código en C# y cuando escribo protocolos serializados utilizo FastSerializer que serializa/deserializa las clases de forma rápida y eficiente. También es muy fácil de usar, y bastante directo para hacer "versiones", es decir, para manejar diferentes versiones de la serialización. Lo que normalmente uso, se ve así:¿Cuál es la mejor forma de manejar versiones utilizando el protocolo JSON?

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>(); 
} 

y

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); 
} 

Así que es fácil de agregar cosas nuevas, o cambiar la serialización por completo si se opta.

Sin embargo, ahora quiero usar JSON por razones que no son relevantes aquí =) Actualmente estoy usando DataContractJsonSerializer y ahora estoy buscando una manera de tener la misma flexibilidad que he utilizando el FastSerializer anteriormente.

Así que la pregunta es; ¿Cuál es la mejor manera de crear un protocolo/serialización JSON y poder detallar la serialización como arriba, para que no rompa la serialización solo porque otra máquina aún no ha actualizado su versión?

Respuesta

14

Google basado en Java gson library tiene un excelente soporte de versiones para json. Podría ser muy útil si piensas ir por Java.

Hay un tutorial agradable y fácil here.

+1

Estoy escribiendo en C#, pero la implementación debería ser posible en cualquier idioma, de lo contrario, se pierde el punto entero ... – Ted

+1

+1 para un buen consejo! – Ted

6

no utilice DataContractJsonSerializer, como su nombre lo dice, los objetos que se procesan a través de esta clase tendrán que:

a) estar marcados con [DataContract] y atributos [DataMember].

b) Sea estrictamente compatible con el "Contrato" definido, es decir, nada menos y nada más que esté definido. Cualquier extra o faltante [DataMember] hará que la deserialización arroje una excepción.

Si quieres ser lo suficientemente flexible, a continuación, utilizar el JavaScriptSerializer si quieres ir a la opción barata ... o utilizar esta biblioteca:

http://json.codeplex.com/

esto le dará suficiente control sobre su Serialización JSON.

Imagine que tiene un objeto en sus comienzos.

public class Customer 
{ 
    public string Name; 

    public string LastName; 
} 

Una vez serializado que se verá así:

{nombre: "Juan", Apellido: "Doe"}

Si cambia de definición de objeto de añadir/quitar campos. La deserialización ocurrirá sin problemas si usa, por ejemplo, JavaScriptSerializer.

public class Customer 
{ 
    public string Name; 

    public string LastName; 

    public int Age; 
} 

Si yo tratar de des-serializar el último JSON a esta nueva clase, se lanzará ningún error. Lo que pasa es que tus nuevos campos se establecerán en sus valores predeterminados. En este ejemplo: "Edad" se establecerá en cero.

Puede incluir, en sus propias convenciones, un campo presente en todos sus objetos, que contiene el número de versión. En este caso, puede ver la diferencia entre un campo vacío o una inconsistencia de versión.

lo que permite decir: Usted tiene su clase v1 cliente serializado:

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

¿Quieres deserializar en una instancia v2 al cliente, que tendrá:

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

Puede alguna manera, detectar qué los campos en su objeto son de alguna manera confiables y lo que no. En este caso, usted sabe que su instancia de objeto v2 proviene de una instancia de objeto v1, por lo que no se debe confiar en el campo Edad.

Tengo en cuenta que también debe usar un atributo personalizado, p. Ej. "MinVersion", y marcar cada campo con el número de versión mínima admitida, para que pueda obtener algo como esto:

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

    [MinVersion(1)] 
    public string Name; 

    [MinVersion(1)] 
    public string LastName; 

    [MinVersion(2)] 
    public int Age; 
} 

Luego, más tarde se puede acceder a este meta-datos y hacer todo lo que pueda necesitar con eso.

+0

Gracias por la sugerencia. Sin embargo, no veo que se maneje ninguna versión específica, excepto por el muy inflado enfoque "ShouldSerialize". ¿Es eso en lo que estás pensando? – Ted

+0

@Ted, solo digo que (a) estos serializadores son lo suficientemente flexibles para que puedan manejar cualquier entrada. (b) si tiene esa flexibilidad, lidiar con el control de versiones es menos crítico. Verifica mi edición –

+0

El problema es que Atributos en C# está bien, pero esto se lee mediante una implementación JAVA en Android, donde no tienen Atributos. – Ted

28

La clave para el control de versiones JSON es siempre agregar nuevas propiedades, y nunca eliminar o cambiar el nombre de las propiedades existentes. Esto es similar a how protocol buffers handle versioning.

Por ejemplo, si usted empieza con el siguiente JSON:

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

Y desea cambiar el nombre de la propiedad "foo" a "barra", no acaba de cambiar su nombre. En su lugar, agregue una nueva propiedad:

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

Como nunca se eliminarán las propiedades, los clientes basados ​​en versiones anteriores seguirán funcionando. La desventaja de este método es que el JSON puede hincharse con el tiempo, y usted debe continuar manteniendo las propiedades antiguas.

También es importante definir claramente sus casos de "borde" para sus clientes. Supongamos que tiene una propiedad de matriz llamada "fooList". La propiedad "fooList" podría asumir los siguientes valores posibles: no existe/indefinido (la propiedad no está físicamente presente en el objeto JSON, o existe y está establecida en "indefinido"), nula, lista vacía o una lista con uno o más valores Es importante que los clientes comprendan cómo comportarse, especialmente en los casos indefinidos/nulos/vacíos.

También recomendaría leer sobre cómo funciona semantic versioning. Si introduce un esquema de versiones semánticas a sus números de versión, entonces se pueden hacer cambios compatibles hacia atrás en un límite de versión menor, mientras que se pueden hacer cambios en un límite de versión principal (tanto los clientes como los servidores deberían ponerse de acuerdo sobre la misma versión principal) Si bien esto no es una propiedad de JSON, esto es útil para comunicar los tipos de cambios que un cliente debe esperar cuando la versión cambia.

+0

, por lo que, ¿está prohibido eliminar propiedades en el nodo JSON? ¿y si mi clase elimina alguna variable? –

+0

JSON no le prohíbe la eliminación de propiedades. PERO, si un cliente consume ese JSON, y una propiedad desaparece repentinamente, ese cliente puede romperse. El objetivo de una estrategia de control de versiones es permitir que una API evolucione al mismo tiempo que mantiene a los clientes estables. – monsur

3

No importa qué protocolo de serialización utilice, las técnicas para las API de versión son generalmente las mismas.

Generalmente se necesita:

  1. una manera para que el consumidor de comunicar al productor de la versión de la API acepta (aunque esto no siempre es posible)
  2. una manera para que el productor para incrustar información de versiones de los datos serializados
  3. una estrategia compatible con versiones anteriores de manejar campos desconocidos

En una API web, en general, la versión de la API de que el consumidor acepta está incrustado en el encabezado Aceptar (p. Accept: application/vnd.myapp-v1+json application/vnd.myapp-v2+json significa que el consumidor puede manejar la versión 1 y la versión 2 de su API) o menos comúnmente en la URL (por ejemplo, https://api.twitter.com/1/statuses/user_timeline.json). Esto generalmente se usa para versiones principales (es decir, cambios incompatibles hacia atrás). Si el servidor y el cliente no tienen un encabezado Accept coincidente, entonces la comunicación falla (o procede en el mejor esfuerzo o de manera alternativa a un protocolo básico predeterminado, dependiendo de la naturaleza de la aplicación).

El productor genera datos serializados en una de las versiones solicitadas, luego inserta esta información de versión en los datos serializados (por ejemplo, como un campo llamado version). El consumidor debe usar la información de versión incorporada en los datos para determinar cómo analizar los datos serializados. La información de versión en los datos también debe contener una versión menor (es decir, para cambios hacia atrás), generalmente los consumidores deberían poder ignorar la información de versión menor y aún procesar los datos correctamente aunque la comprensión de la versión menor puede permitir al cliente hacer suposiciones adicionales sobre cómo deberían procesarse los datos

Una estrategia común para manejar campos desconocidos es como la forma de analizar HTML y CSS. Cuando el consumidor ve un campo desconocido, debe ignorarlo, y cuando a los datos le falta un campo que el cliente espera, debe usar un valor predeterminado; dependiendo de la naturaleza de la comunicación, es posible que también desee especificar algunos campos que son obligatorios (es decir, los campos faltantes se consideran errores fatales). Los campos agregados en versiones menores siempre deben ser campos opcionales; la versión menor puede agregar campos opcionales o cambiar los campos semánticos siempre que sea retrocompatible, mientras que la versión principal puede eliminar campos o agregar campos obligatorios o cambiar campos semánticamente de manera incompatible hacia atrás.

En un formato de serialización extensible (como JSON o XML), los datos deben ser autodescriptivos, en otras palabras, los nombres de los campos siempre se deben almacenar junto con los datos; no debe confiar en que los datos específicos estén disponibles en posiciones específicas.

Cuestiones relacionadas