viernes, 26 de noviembre de 2010

Serialización y deserialización de JSON

En un anterior post vimos que podíamos serializar a JSON con las clases JavascriptSerializer y DataContractSerializer, pero en este post intentaremos entender un poco mejor cuando usar una u otra clase y cuales son sus ventajas y desventajas.

Vamos a trabajar con una clase Coche con las propiedades Marca (String), Deportivo (Boolean) y Caballos (Integer).

Public Class Coche

 

    Private _marca As String

    Public Property Marca() As String

        Get

            Return _marca

        End Get

        Set(ByVal value As String)

            _marca = value

        End Set

    End Property

 

    Private _deportivo As Boolean

    Public Property Deportivo() As Boolean

        Get

            Return _deportivo

        End Get

        Set(ByVal value As Boolean)

            _deportivo = value

        End Set

    End Property

 

    Private _caballos As Integer

    Public Property Caballos() As Integer

        Get

            Return _caballos

        End Get

        Set(ByVal value As Integer)

            _caballos = value

        End Set

    End Property

 

End Class

 

Para trabajar con DataContractJsonSerializer es necesario agregar referencias a los ensamblados System.Runtime.Serialization y System.ServiceModel.Web.

Primero utilizaremos la clase System.Web.Script.Serialization.JavaScriptSerializer y después de la clase System.Runtime.Serialization.Json.DataContractJsonSerializer.

Primero JavaScriptSerializer:

Dim seat As New Coche

seat.Marca = "León"

seat.Deportivo = False

seat.Caballos = 110

Dim serializador As New System.Web.Script.Serialization.JavaScriptSerializer

Dim sb As New System.Text.StringBuilder

serializador.Serialize(seat, sb)

 

{"Marca":"León","Deportivo":false,"Caballos":110}

Como vemos no ha sido necesario adornar la clase con el atributo <Serializable()> ni especificar al serializador cual es el tipo del objeto que estamos serializando, y esto es porque JavascriptSerializer utiliza reflexión.

Segundo DataContractJsonSerializer:

Dim serializador As New System.Runtime.Serialization.Json.DataContractJsonSerializer(seat.GetType)

Dim ms As New System.IO.MemoryStream

serializador.WriteObject(ms, seat)

Dim textoJson As String = System.Text.Encoding.UTF8.GetString(ms.ToArray)

 

{"Caballos":110,"Deportivo":false,"Marca":"León"}

Como vemos el resultado es el mismo (o casi porque el orden las propiedades ha cambiado…) y además tampoco ha sido necesario adornar la clase con <Serializable()> ni <DataContract()> para serializar con este serializador, de nuevo utiliza reflexión. Por otro lado, si ha sido necesario especificar el tipo del objeto a serializar porque DataContractJsonSerializer.

Si agregamos el atributo <Serializable()> a nuestro clase Coche ahora los resultados son distintos:

JavascriptSerializer

{"Marca":"León","Deportivo":false,"Caballos":110}

DataContractJsonSerializer

{"_caballos":110,"_deportivo":false,"_marca":"León"}

 

Hay que fijarse que DataContractJsonSerializer parece que no se entiende muy bien con el atributo <Serializable()> y ha tomado el nombre de los campos privados en vez de el nombre de las propiedades públicas.

Si en vez de <Serializable()> agregamos <DataContract> a nuestra clase:

JavascriptSerializer

{"Marca":"León","Deportivo":false,"Caballos":110}

DataContractJsonSerializer

{}

 

Esto significa que JavascriptSerializer no utiliza el atributo <DataContract()>, pero DataContractJsonSerializer si lo utiliza y entonces espera que cada propiedad serializable esté marcada con el atributo <DataMember()>. De este modo, agregamos el atributo <DataMember()> a las propiedades de nuestra clase. Y ahora obtenemos:

JavascriptSerializer

{"Marca":"León","Deportivo":false,"Caballos":110}

DataContractJsonSerializer

{"Caballos":110,"Deportivo":false,"Marca":"León"}

 

Además el atributo DataMember nos permite especificar con que nombre de campo se serializará la propiedad, el orden, etc. http://msdn.microsoft.com/es-es/library/ms574795(v=VS.90).aspx

Por ejemplo, y serializando con DataContractJsonSerializer y con el siguiente adorno para la propiedad Marca: <System.Runtime.Serialization.DataMember(Name:="MiMarca")> _

El resultado será {"Caballos":110,"Deportivo":false,"MiMarca":"León"}.

 

Por otro lado, una importante característica que soporta JavascriptSerializer y que no soporta DataContractJsonSerializer es que JavascriptSerializer puede deserializar a un objeto diccionario además de a un tipo concreto. Esto significa que si nos llega un código Json desde cliente y no tenemos ninguna clase equivalente en la que deserializar, sólo podremos usar JavascriptSerializer para llevar a cabo esta operación (puesto que DataContractJsonSerializer siempre nos pide un tipo).

        Dim texto As String = "{""Marca"":""León"",""Deportivo"":false,""Caballos"":110}"

 

        'Conozco el tipo coche...

        Dim nuevoCoche As Coche = serializador.Deserialize(Of Coche)(texto)

 

        'No conozco el tipo coche...

        Dim diccionario As Dictionary(Of String, Object) = _

            CType(serializador.DeserializeObject(texto), Dictionary(Of String, Object))

clip_image002

Ahora haremos que nuestra clase tenga además una referencia a un objeto de tipo Volante:

Public Class Coche

 

    Private _marca As String

 

    Public Property Marca() As String

        Get

            Return _marca

        End Get

        Set(ByVal value As String)

            _marca = value

        End Set

    End Property

 

    Private _deportivo As Boolean

    Public Property Deportivo() As Boolean

        Get

            Return _deportivo

        End Get

        Set(ByVal value As Boolean)

            _deportivo = value

        End Set

    End Property

 

    Private _caballos As Integer

    Public Property Caballos() As Integer

        Get

            Return _caballos

        End Get

        Set(ByVal value As Integer)

            _caballos = value

        End Set

    End Property

 

    Private _volante As Volante

    Public Property Volante() As Volante

        Get

            Return _volante

        End Get

        Set(ByVal value As Volante)

            _volante = value

        End Set

    End Property

 

 

End Class

 

Public Class Volante

 

    Private _forma As String

    Public Property Forma() As String

        Get

            Return _forma

        End Get

        Set(ByVal value As String)

            _forma = value

        End Set

    End Property

 

    Private _radios As Integer

    Public Property Radios() As Integer

        Get

            Return _radios

        End Get

        Set(ByVal value As Integer)

            _radios = value

        End Set

    End Property

 

End Class

 

JavascriptSerializer

{"Marca":"León","Deportivo":false,"Caballos":110,"Volante":{"Forma":"Redonda","Radios":3}}

DataContractJsonSerializer

{"Caballos":110,"Deportivo":false,"Marca":"León","Volante":{"Forma":"Redonda","Radios":3}}

 

Y deserializando si conocer el tipo, tampoco hay ningún problema:

 

clip_image004

A raíz de todo lo expuesto podemos sacar las siguientes conclusiones:

  • En comienzo (para una clase sin adornos), ambas clases (JavascriptSerializer y DataContractJsonSerializer) ofrecen el mismo texto Json de salida.
  • Si nuestra clase está marcada como <Serializable()>, JavascriptSerializer funcionará correctamente pero entonces ya será necesario agregar también el adorno <DataContract()> y <DataMember()> para que siga funciona correctamente DataContractJsonSerializer.
  • A través del atributo <DataMember()> es posible configurar ciertos aspectos de la serialización como el nombre, el orden, etc.
  • Si no se tiene un tipo válido para la deserialización, JavascriptSerializer ofrece una alternativa deserializando a un objeto diccionario
  • Supuestamente JavascriptSerializer está marcado como obsoleta así que Microsoft recomienda el uso de DataContractJsonSerializer. 

Por último y si utilizamos ASP.NET AJAX, tenemos disponibles en cliente ciertos métodos para serializar y deserializar datos en Json. Para ello utilizaremos la clase (desde javascript) Sys.Serialization.JavaScriptSerializer y sus métodos serialize y deserialize. http://msdn.microsoft.com/en-us/library/bb310857.aspx

<asp:ScriptManager ID="ScriptManager1" runat="server">

    </asp:ScriptManager>

    <div>

        <asp:HiddenField ID="HiddenField1" runat="server" />

        <asp:HiddenField ID="HiddenField2" runat="server" />

        <script type="text/javascript">

            function pageLoad(sender, e) {

                //recoger texto json generado desde el servidor

                var value = $get("<%=HiddenField1.ClientID %>").value;

                //deserializar a través de ASP.NET AJAX

                var coche = Sys.Serialization.JavaScriptSerializer.deserialize(value);

                alert(coche.Caballos);               

            }

        </script>

    </div>

 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim seat As New Coche

        seat.Marca = "León"

        seat.Deportivo = False

        seat.Caballos = 110

        seat.Volante = New Volante

        seat.Volante.Forma = "Redonda"

        seat.Volante.Radios = 3

        Dim serializador As New System.Web.Script.Serialization.JavaScriptSerializer

        Dim sb As New System.Text.StringBuilder

        serializador.Serialize(seat, sb)

        HiddenField1.Value = sb.ToString

    End Sub

 

También podemos deserializar json con javascript, bien sea a pelo, (que no se recomienda porque supone un riego de seguridad), con jQuery y también a través de los métodos nativos del navegador http://www.javascriptkit.com/jsref/json.shtml (ojo!, que sólo los navegadores modernos lo incluyen).

//deserializar con jQuery

coche = $.parseJSON(value);

alert(coche.Caballos);

 

//deserializar con javascript

/*

NO se recomienda porque podría suponer un problema de seguridad!!

*/

coche = eval("(" + value + ");");

alert(coche.Caballos);

 

Por último, desde http://www.json.org/js.html podemos ver cual es la sintaxis JSON y además descargarnos el fichero http://www.json.org/json2.js que nos ayudará a serializar y deserializar en cliente (porque no siempre tendremos ASP.NET AJAX y su framework de cliente disponible, o por otro lado tampoco tendremos la certeza de estar ejecutando una de las últimas versiones del navegador de turno).

Este fichero expone sólo 2 métodos (igual que el objeto nativo JSON que incluyen los navegadores modernos): stringify (para conseguir la representación en JSON de un objeto) y parse (para conseguir un objeto desde su representación en JSON). Sus llamadas básicas (que serán el 99% de las veces) son las siguientes:

//deserializar

coche = JSON.parse(value);

alert(coche.Caballos);

 

//serializar

value = JSON.stringify(coche);

alert(value);

 

Un saludo!