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!

2 comentarios:

  1. Hola, gracias por tu post, me ha sido muuy util para enviar una respuesta JSON desde web services escritos en VB.NET.

    Ahora necesito hacer en el cliente VB.NET 2010, la función que deserialize la respuesta JSON.

    Me puedes ayudar con respecto a como seria la función para deserializar la respuesta JSON?

    Mi función para serializar dentro de una DLL quedó asi:

    Public Function ManageMobileUsers(UsrNmb As String, UsrPwd As String) As String

    Dim TheReturnValue As New ReturnValues
    Dim TheMobileUser As New MobileUser

    TheReturnValue = ManageUsers(UsrNmb, UsrPwd)
    TheMobileUser = TheReturnValue.MobileUser

    Dim Serializador As New System.Runtime.Serialization.Json.DataContractJsonSerializer(TheMobileUser.GetType)

    Dim Ms As New System.IO.MemoryStream

    Serializador.WriteObject(Ms, TheMobileUser)

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

    Return TheStringJson

    End Function

    El Web Service quedó así:

    _
    _
    Public Function ManageMobileUsers(ByVal UsrNmb As String, ByVal UsrPwd As String, ByVal Release As String) As String

    Dim appm As AppMembers = New AppMembers(UsrNmb)

    Return appm.DBProc.ManageMobileUsers(UsrNmb, UsrPwd) ' Esta función ya devuelve JSON

    End Function


    Esta es la clase que serializo y necesito deserializar:

    Public Class MobileUser

    Private _status As String

    Public Property status() As String

    Get

    Return _status

    End Get

    Set(ByVal value As String)

    _status = value

    End Set

    End Property

    Private _userId As String

    Public Property userId() As String

    Get

    Return _userId

    End Get

    Set(ByVal value As String)

    _userId = value

    End Set

    End Property

    Private _name As String

    Public Property name() As String

    Get

    Return _name

    End Get

    Set(ByVal value As String)

    _name = value

    End Set

    End Property

    Private _rol As Integer

    Public Property rol() As Integer

    Get

    Return _rol

    End Get

    Set(ByVal value As Integer)

    _rol = value

    End Set

    End Property

    End Class


    Muchas gracias.

    Luis

    ResponderEliminar
  2. Muchas gracias me ha sido de gran ayuda!.

    ResponderEliminar