Optimisation de la serialisation json pour les list<t>

Description

Cette source permet d'optimiser la sérialisation JSON d'une List<T>. Par défaut si un WebService retourne une List<T> alors toutes les propriétés seront répétés pour chacun des objets.
On obtient alors un JSON ressemblant à :

{"d": [
{
"__type":"Person:#",
"BirthDate":"\/Date(1211790363564+0200)\/",
"City":"Chénas",
"FirstName":"Steven",
"LastName":"Vincent"
},{
"__type":"Person:#",
"BirthDate":"\/Date(1211790441107+0200)\/",
"City":"Légny",
"FirstName":"Janet",
"LastName":"laurent"
},{
// etc...

Cette astuce permet d'avoir un json ressemblant à :

{"d":{
"Columns":[
"FirstName",
"LastName",
"BirthDate",
"City"
],"Values":[
["Andrew","Alex","\/Date(1211790586937+0200)\/","Saint-Didier-sur-Beaujeu"],
["Laura","Claude","\/Date(1211790697591+0200)\/","Chénas"],
["Anne","Isabelle","\/Date(1211790655756+0200)\/","Saint-Cyr-le-Chatoux"],
["Nancy","Steph","\/Date(1211790592372+0200)\/","Sainte-Paule"],
// etc

On obtient un gain de traffic de l'ordre de 50%.

Source / Exemple :


using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Reflection;

/// <summary>
/// Permet d'optimiser la serailisation JSON d'une List<T>
/// </summary>
/// <typeparam name="T"></typeparam>
[DataContract]
public class JsonList<T>
{

    #region Constructors

    private static IEnumerable<String> _columns;
    private static IEnumerable<PropertyInfo> _properties;

    /// <summary>
    /// Ce constructeur sera appelé lors de la premiere utilisation du type "finale" => pour chaque T
    /// </summary>
    static JsonList()
    {
        // mis en cache de la liste des colonne en fonction du type T
        // TODO : Je ne prend pas en compte toutes les options du DataMemberAttribute, il y a donc des bugs si on joue avec des cas spécifique
        _properties = (from property in typeof(T).GetProperties()
                       let dataMemberAttributes = (DataMemberAttribute[])property.GetCustomAttributes(typeof(DataMemberAttribute), false)
                       where dataMemberAttributes.Length > 0
                       select property
                   ).ToList(); // ToList pour pas garder le IEnumerable et donc refaire la requête à chaque fois

        _columns = (from property in _properties
                    // on pourrait mettre la recherche de l'attribut en cache à partir de la requete précedente, 
                    // mais cela nécessite de créer une structure rien que pour ca ...
                    let dataMemberAttribute = ((DataMemberAttribute[])property.GetCustomAttributes(typeof(DataMemberAttribute), false))[0]
                    select !String.IsNullOrEmpty(dataMemberAttribute.Name) ? dataMemberAttribute.Name : property.Name
                   ).ToList(); // ToList pour pas garder le IEnumerable et donc refaire la requête à chaque fois
    }

    private IEnumerable<T> _innerList;

    public JsonList(IEnumerable<T> innerList)
    {
        this._innerList = innerList;
    }

    #endregion

    #region implicit casting operator

    public static implicit operator JsonList<T>(List<T> items)
    {
        return new JsonList<T>(items);
    }
    public static implicit operator JsonList<T>(T[] items)
    {
        return new JsonList<T>(items);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Contient un tableau contenant le nom des différents colonnes
    /// </summary>
    [DataMember]
    public IEnumerable<String> Columns
    {
        get { return _columns; }
        set { throw new NotSupportedException("Deserialization of this object is not supporter yet"); }
    }

    /// <summary>
    /// Contient un tableau de tableau contenant les valeurs de la list 
    /// </summary>
    /// <remarks>
    /// [
    ///   ['FirstName1', 'LastName1'],
    ///   ['FirstName2', 'LastName2']
    /// ]
    /// </remarks>
    [DataMember]
    public IEnumerable<IEnumerable<Object>> Values
    {
        get
        {
            // TODO : optimiser ca en utilisant les Expression de C#3 afin de mettre en cache le getter
            return from item in this._innerList
                   select from property in _properties
                          select property.GetValue(item, null);
        }
        set { throw new NotSupportedException("Deserialization of this object is not supporter yet"); }
    }

    #endregion
}

Conclusion :


L'exemple fourni dans le zip montre la différence entre la serialisation classique et l'optimisation.

J'ai utilisé un service WCF, mais ce code devrait également fonctionner pour les services web asmx classique, il faut juste rajouter un attribut DataMember sur les propriétés à sérializer.

Je ne prend pas en compte toutes les possibilités de l'attribut DataMember de WCF, il se peut qu'il y ait quelques bugs si on n'utilise pas cet attribut comme je l'ai prévu :)

Plus d'explication sont disponible ici : http://blogs.developpeur.org/cyril/archive/2008/05/26/json-optimiser-serialisation-list-aspnet-ajax.aspx

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.