Data.AESON

De Wikihaskell
Saltar a: navegación, buscar
Data.AESON
Data.AESON
Lenguaje Haskell
Biblioteca Data.AESON
Autores Roberto García Carvajal
Lola Martínez Jurado
Jose Manuel Llerena Carmona

Contenido

Introducción

Dentro de los diferentes formatos de intercambio de datos que existen actualmente, JSON es uno de los que más se están generalizando debido a su ligereza y simplicidad (como importante alternativa a tener en cuenta respecto a XML).

En este punto, dentro de las diferentes bibliotecas que podemos encontrar en Haskell para trabajar con JSON encontramos Data.AESON. Se trata de una librería para parsear y codificar JSON, optimizada para un uso fácil y con un alto rendimiento. Permite la codificación y decodificación tanto de estructuras simples y conocidas, como de estructuras de datos complejas. Este paquete para el tratamiento de datos JSON está desarrollado por Bryan O'Sullivan, y actualmente se encuentra en fase experimental, por lo que hay que tener precaución a la hora de usarlo ya que pueden existir errores o inconsistencias.

¿Qué es JSON?

JSON es un formato de estándar abierto, para el intercambio de datos, y es uno de los principalmente usados en Internet, sirviendo como una importante alternativa a XML. Este formato está derivado de JavaScript para la representación de estructuras de datos simples y objetos. Este formato fue desarrollado originalmente por el programador informático Douglas Crockford y está descrito en el RFC 4627. Para más información, además del RFC, se puede consultar la página oficial de JSON, la cual se encuentra indicada en el apartado de referencias.

Instalación

Damos por echo que el usuario ya tiene instalado su compilador de Haskell (por ejemplo GHC), en Haskell para realizar la instalación de los distintos módulos disponibles en el repositorio de Haskell, se necesitará la herramienta Cabal, la cual deberemos de tener instalada previamente en el sistema.

Este sistema permite construir y empaquetar programas y librerías Haskell y forma parte de la infraestructura para la distribución, organización y catalogación de los programas y librerias Haskell.

Instalación de Cabal

A continuación se describe el proceso de instalación de la aplicación cabal para la instalación de paquetes para Haskell.

Instalación en Windows

Para instalar cabal en sistemas Windows, acceda a la siguiente dirección donde podrá descargar el fichero ejecutable de cabal para este tipo de sistemas. Una vez haya descargado el fichero ejecutable cabal.exe, coloque este fichero en un directorio que se encuentre en el PATH de Windows, o bien, añada el directorio donde ha almacenado cabal, al PATH de Windows.

Instalación en Linux

Para la instalación en linux, si nos encontramos en una distribución basada en Debian, podemos instalar cabal desde la terminal de la siguiente forma:

sudo apt-get install cabal-install

Esto se encargará de instalar cabal y el resto de dependencias que sean necesarias. Una vez se haya instalado, para instalar cualquier módulo solo tenemos que hacer:

cabal install nombre_paquete

Instalando Cabal desde los fuentes

1- Instalamos las dependencias que seguramente necesitaremos:

sudo apt-get install ghc

2- Descargamos el intérprete de la siguiente dirección, lo descomprimimos, y desde dentro del directorio ejecutamos los siguientes comandos:

chmod u+x bootstrap.sh
./bootstrap.sh

3- Una vez hecho esto, copiamos el ejecutable al path principal:

sudo cp ~/.cabal/bin/cabal /usr/local/bin/

4- Una vez hecho esto, solo tendremos que actualizar cabal para que descargue/actualice la información de los paquetes de Haskell:

cabal update

5- El siguiente paso es descargar e instalar las librerías de Haskell, para ello lo descargamos de la siguiente dirección y lo descomprimimos en cualquier directorio. Una vez descomprimido, desde dentro del directorio ejecutamos los sigueintes comandos:

  • Si no tenemos permisos de administrador:
ghc --make Setup
./Setup configure --user
./Setup build
./Setup install
  • Si tenemos permisos de administrador:
ghc --make Setup
./Setup configure
./Setup build
sudo ./Setup install

Y ya tenemos cabal instalado.

Instalación del módulo AESON

Para realizar la instalación del módulo aeson, una vez haya instalado la aplicación cabal, solo tiene que ejecutar el siguiente comando en su terminal.

cabal install aeson

Primeros pasos

¿Cómo podemos trabajar con este paquete a la hora de codificar y decodificar datos?. La versatilidad de esta biblioteca permite ir de lo simple a lo complejo, dotando al sistema de flexibilidad o no en función de esta complejidad. De esta forma, con el paquete AESON se puede trabajar con datos sencillos (como enteros, caracteres, etc...) o estructuras de datos conocidas (como pueden ser listas o tuplas), de forma directa, utilizando las funciones disponibles para la codificación y decodificación, como con estructuras de datos complejas, las cuales hayan sido definidas por el propio usuario, permitiendo la serialización de las mismas. Para ello el usuario tiene la posibilidad de proporcionar los esquemas de codificación y decodificación en función de la estructura de datos.

Decode.jpg

Para usar la librería lo primero que debemos hacer es importar el paquete Data.Aeson, para ello debemos hacer lo siguiente:

Desde un fichero el cual importaremos posteriormente:

   import Data.Aeson

Desde el intérprete de Haskell (como por ejemplo GHCi):

   Prelude> :m + Data.Aeson

A continuación, ya solo queda codificar o decodificar, lo cual se hace de forma muy sencilla:

   -- Para codificar
   Prelude Data.Aeson> encode [1,2,3]
   "[1,2,3]"
   -- Para decodificar
   Prelude Data.Aeson> decode (encode [1,2,3]) :: Maybe [Integer]
   Just [1,2,3]

Como se puede apreciar la codificación y decodificación es bastante sencilla, basta con indicar el elemento que queremos codificar (caso de que se conozca su esquema de codificación) o indicar el elemento que queremos decodificar y la estructura del elemento que deberá de tener el elemento que queremos decodificar (en este caso es una lista de enteros).

Introducción al lenguaje de plantillas

Para poder hacer un uso completo de este paquete de Haskell, será necesario el conocimiento, al menos básico, del lenguaje de plantillas que posee Haskell. El módulo TH (Template Haskell) del paquete AESON, hace referencia complemetamente a funciones para el lenguaje de plantillas.

¿Qué es concretamente el lenguaje de plantillas?

El lenguaje de plantillas de Haskell es una extensión de GHC ([Glasgow Haskell Compiler][1]), el cual hace posible que se pueda generar nuevo código en tiempo de compilación. El lenguaje de plantillas puede llegar a ser muy complejo, pero por suerte para el usuario, en este punto solo nos interesa como utilizar las funciones que nos proporciona el paquete AESON, por lo que solo son unas pocas cosas las que debemos aprender.

Funcionamiento

Para indicarle al compilador GHC que estamos haciendo uso del lenguaje de plantillas en el código fuente que queremos compilar, únicamente tendremos que incluir en la parte superior de nuestro código fuente la siguiente directiva:

   {-# LANGUAGE TemplateHaskell #-}

Una vez que hemos activado el lenguaje de plantillas, aparecen unos nuevos elementos sintácticos los cuales podremos utilizar en nuestro código:

  • $( ): Este elemento sintáctico se usa para indicar que el código que se encuentre entre los paréntesis va a generar código en tiempo de compilación. Una vez se realice la compilación, el elemento $(..) se reemplazará por el código generado, y posteriormente se compilará el código. El uso de este elemento, se ha vuelto opcional desde la versión del GHC 6.12 o superiores.
  • ': La comilla simple, devuelve el nombre de una función o un constructor que se hará uso en la plantilla. El elemento que devuelve viene descrito en el apartado de plantillas de Haskell (Language.Haskell.TH.Syntax.Name).
  • ' ': Las dobles comillas simples, tienen la misma finalidad que la comilla simple, pero en este caso se utiliza para devolver el nombre de un tipo, en vez de una función o constructor.

Módulos

A continuación se muestra las distintas funcionalidades que hay implementadas en cada uno de los módulos de los que está compuesto el paquete AESON.

Encode

Este módulo contiene el conjunto de funciones necesarias para serializar una estructura de datos u objeto en su código JSON correspondiente.


fromValue

Esta función se usa para codificar un valor JSON del tipo Value al tipo Builder. El tipo Biulder está definido dentro del paquete Data.Text.Lazy y puede convertirse a un string haciendo uso de la función toLazyText o codificándolo directamente a el formato UTF-8. La definición de la función es como sigue:

fromValue :: Value -> Builder

encode

Esta función se usa para serializar un determinado objeto o estructura de datos en su JSON equivalente, devolviendo este como un objeto de tipo lazy ByteString.

La codificación es un proceso de 2 pasos, de manera que para codificar un valor primero debemos convertirlo a un AST (abstract syntax tree o árbol de sintaxis abstracta) usando la función ToJSON, después codificamos esta representación genérica en forma de bytes.

La estructura de la función es la siguiente:

encode :: ToJSON a => a -> ByteString


El elemento ToJSON es un tipo específico para el cual definiremos la forma de realizar la traducción de la estructura de datos u objeto que queramos serializar a JSON, de forma que sepa como transformar este. El funcionamiento del elemento ToJSON se puede ver con más profundidad en el módulo de Types.

Generic

Este módulo implementa varias funciones útiles para realizar la codificación y decodificación de estructuras de datos desde y hacia JSON y otras funciones de conversiones a bajo nivel. Los datos con los que trabaja dichas funciones deben de ser instancias de la clase Generic de Haskell, la cual fue escrita originalmente por Lennart Augustsson.

Codificación

encode :: Data a => a -> ByteString


Serializa eficientemente un valor JSON como un elemento de tipo lazy ByteSting.

Decodificación

En este módulo se presentan dos funciones alternativas para la decodificación de JSON, donde las funciones producen el mismo resultado, pero la implementación es distinta.


La declaración de estas funciones son de la siguiente forma:

decode :: Data a => ByteString -> Maybe a
 
decode' :: Data a => ByteString -> Maybe a


El argumento Maybe a del final, se utiliza para indicarle a la función la estructura de los datos que queremos decodificar, para que este pueda decodificarlo en el mismo formato que la indicamos, siempre que esté definida esta conversión. Por ejemplo, si tuviéramos el JSON de una lista de entero, en este caso deberíamos de indicarlo con Maybe [Integer].

Estas dos funciones realizan la conversión eficiente de forma inmediata desde un elemento de tipo lazy ByteString.

La diferencia de uno y otro es que decode realiza una conversión de forma perezosa y decode' de forma estricta. El uso de un tipo u otro, se aprecia la diferencia en cuanto a consumo de ciclos de CPU y de memoria interna.

Funciones para conversiones a bajo nivel

Generic proporciona las siguiente funciones para la conversión a bajo nivel.

fromJSON :: Data a => Value -> Result a
 
toJSON :: Data a => a -> Value

Parser

Este módulo se encarga de parsear una cadena JSON correctamente y eficientemente.

Con la siguiente función se parsea un valor JSON de alto nivel. Debe ser un objeto o un array.

  1. json :: Parser Value


Con la siguiente función se parsea cualquier valor JSON. Se debe usar la función 'json' en lugar de esta si se están parseando datos no verificados.

  1. value :: Parser Value

TH

Este módulo del paquete AESON, están dedicado al uso de plantillas en Haskell, de aquí su nombre TH (Template Haskell). En él se describen una serie de funciones, para derivar de forma mecánica las instancias ToJSON y FromJSON, necesarias para la serialización y deserialización de objetos definidos por los usuarios. Esto se trata de una gran ayuda para el usuario del paquete, ya que en ocasiones, y principalmente cuando trabajamos con un tipo de datos de considerable tamaño, los esquemas de traducción pueden se bastante complejos de diseñar y requieren de bastante práctica por parte del usuario.

Tenga en cuenta, que como acabamos de decir, en este apartado se hará uso del lenguaje de plantillas de Haskell, por lo que será necesario incluir la siguiente cabecera al principio de cada documento donde se vaya a incluir lenguaje de plantillas, de forma que el compilador de Haskell pueda saber interpretar el lenguaje de plantillas (esto aparece mas detallado en el apartado de Introducción al lenguaje de plantillas):

   {-# LANGUAGE TemplateHaskell #-}

A continuación se describen cada una de las funciones existentes para la generación de los esquemas de traducción de las estructuras de datos.

deriveJSON

Esta función se utiliza para generar las declaraciones de las instancias ToJSON and FromJSON para el tipo de datos que se le indique. La llamada a esta función es similar a llamar a las funciones deriveToJSON y deriveFromJSON.

La cabecera de esta función es del siguiente tipo:

deriveJSON :: (String -> String) -> Name -> Q [Dec]

Donde cada uno de los argumentos son:

   (String -> String)  -- Función para cambiar el nombre de los fields de la estructura de datos.
    Name               -- Nombre del tipo de datos del que se quiere generar las instancias ToJSON y FromJSON.

Ejemplo de uso

Un ejemplo de uso de esta función con el lenguaje de plantillas, sería el siguiente:

data Foo = Foo Char Int
$(deriveJSON id ''Foo)

Para lo que se generaría los siguientes esquemas de traducción en ambos sentidos:

  1. instance ToJSON Foo where
  2.     toJSON =
  3.         value -> case value of
  4.                     Foo arg1 arg2 -> Array $ create $ do
  5.                       mv <- unsafeNew 2
  6.                       unsafeWrite mv 0 (toJSON arg1)
  7.                       unsafeWrite mv 1 (toJSON arg2)
  8.                       return mv
  9.  
  10. instance FromJSON Foo where
  11.     parseJSON =
  12.         value -> case value of
  13.                     Array arr ->
  14.                       if (V.length arr == 2)
  15.                       then Foo <$> parseJSON (arr unsafeIndex 0)
  16.                                <*> parseJSON (arr unsafeIndex 1)
  17.                       else fail "<error message>"
  18.                     other -> fail "<error message>"

deriveToJSON

Esta función se utiliza para generar automáticamente la declaración de la instancia ToJSON para el tipo de datos que se le indique, necesaria para serialización de la estructura de datos a JSON. La cabecera de esta función tiene el siguiente tipo:

deriveToJSON :: (String -> String) -> Name -> Q [Dec]

Donde cada uno de los argumentos son:

   (String -> String)  -- Función para cambiar el nombre de los fields de la estructura de datos.
    Name               -- Nombre del tipo de datos del que se quiere generar la instancia ToJSON.

Ejemplo de uso

Un ejemplo de uso de esta función con el lenguaje de plantillas, sería el siguiente:

data Foo = Foo Char Int
$(deriveToJSON id ''Foo)


Para lo que se generaría el siguiente esquema de traducción:

  1. instance ToJSON Foo where
  2.     toJSON =
  3.         value -> case value of
  4.                     Foo arg1 arg2 -> Array $ create $ do
  5.                       mv <- unsafeNew 2
  6.                       unsafeWrite mv 0 (toJSON arg1)
  7.                       unsafeWrite mv 1 (toJSON arg2)
  8.                       return mv

deriveFromJSON

Esta función se utiliza para generar automáticamente la declaración de la instancia FromJSON para el tipo de datos que se le indique, necesaria para deserialización del código JSON a la estructura de datos. La cabecera de esta función tiene el siguiente tipo:

deriveFromJSON :: (String -> String) -> Name -> Q [Dec]

Donde cada uno de los argumentos son:

   (String -> String)  -- Función para cambiar el nombre de los fields de la estructura de datos.
    Name               -- Nombre del tipo de datos del que se quiere generar la instancia FromJSON.

Ejemplo de uso

Un ejemplo de uso de esta función con el lenguaje de plantillas, sería el siguiente:

data Foo = Foo Char Int
$(deriveFromJSON id ''Foo)

Para lo que se generaría el siguiente esquema de traducción:

  1. instance FromJSON Foo where
  2.     parseJSON =
  3.         value -> case value of
  4.                     Array arr ->
  5.                       if (V.length arr == 2)
  6.                       then Foo <$> parseJSON (arr unsafeIndex 0)
  7.                                <*> parseJSON (arr unsafeIndex 1)
  8.                       else fail "<error message>"
  9.                     other -> fail "<error message>"

mkToJSON

Esta función devuelve una expresión lambda la cual codifica automáticamente el tipo de datos indicado a JSON. La cabecera de esta función tiene el siguiente tipo:

mkToJSON :: (String -> String) -> Name -> Q Dec

Donde cada uno de los argumentos son:

   (String -> String)  -- Función para cambiar el nombre de los fields de la estructura de datos.
    Name               -- Nombre del tipo de datos del que se quiere generar la función lambda para codificar.

Ejemplo de uso

Un ejemplo de uso de esta función con el lenguaje de plantillas, sería el siguiente:

data Foo = Foo Char Int
encodeFoo = $(mkToJSON id ' 'Foo)

De tal forma que encodeFoo será una función lambda la cual permitirá codificar elementos del tipo Foo a JSON.

mkParseJSON

Esta función devuelve una expresión lambda la cual codifica automáticamente el código JSON al tipo de datos indicado. La cabecera de esta función tiene el siguiente tipo:

mkParseJSON :: (String -> String) -> Name -> Q Dec

Donde cada uno de los argumentos son:

   (String -> String)  -- Función para cambiar el nombre de los fields de la estructura de datos.
    Name               -- Nombre del tipo de datos del que se quiere generar la función lambda para decodificar.

Ejemplo de uso

Un ejemplo de uso de esta función con el lenguaje de plantillas, sería el siguiente:

data Foo = Foo Char Int
parseFoo = $(mkParseJSON id ' 'Foo)

De tal forma que parseFoo será una función lambda la cual permitirá decodificar código JSON a elementos del tipo Foo.

Types

Paquete de tipos para trabajar con JSON.

Tipos principales

Representación en Haskell de un tipo JSON.

data Value

Constructor

  1. data Value = Object !Object
  2.        | Array !Array
  3.        | String !Text
  4.        | Number !Number
  5.        | Bool !Bool
  6.        | Null
  7.          deriving (Eq, Show, Typeable)

Instancias

  1. Eq Value	 
  2. Show Value	 
  3. Typeable Value	 
  4. IsString Value	 
  5. NFData Value	 
  6. Hashable Value	 
  7. FromJSON Value	 
  8. ToJSON Value

Una secuencia JSON.

type Array = Vector Value

La secuencia vacía.

emptyArray :: Value
emptyArray = Array V.empty

Una pareja clave/valor de un objeto.

type Pair = (Text, Value)

Un objeto JSON (diccionario) con clave/valor.

type Object = HashMap Text Value

El objeto vacío.

emptyObject :: Value
emptyObject = Object H.empty

Tipos propios

newtype DotNetTime

Contenedor newtime para UTCTime.

Constructor

  1. newtype DotNetTime = DotNetTime {
  2.     fromDotNetTime :: UTCTime
  3. } deriving (Eq, Ord, Read, Show, Typeable, FormatTime)

Utiliza el mismo formato de serialización no standar que usa Microsoft .Net cuyo tipo System.DateTime es por defecto serilizable a JSON. Cómo ejemplo:

  /Date(1302547608878)/ -- (los numeros representan los milisegundos)

Instancias

  1. Eq DotNetTime	 
  2. Ord DotNetTime	 
  3. Read DotNetTime	 
  4. Show DotNetTime	 
  5. Typeable DotNetTime	 
  6. FormatTime DotNetTime	 
  7. FromJSON DotNetTime	 
  8. ToJSON DotNetTime

Conversión de tipos

FromJSON

Clase para la transformación de tipos desde elementos JSON. Permite chequear errores.

class FromJSON a where
    parseJSON :: Value -> Parser a

Cuando escribimos una instancia se usa empty, mzero o fail para hacer la conversión de fallo ( por ejemplo si un objeto le falta una key requerida o el valor es de un tipo incorrecto)

Veamos el siguiente ejemplo de tipo e instancia:

  1. {-# LANGUAGE OverloadedStrings #-}
  2.  
  3. data Coord { x :: Double, y :: Double }
  4.  
  5. instance FromJSON Coord where
  6.     parseJSON (Object v) = Coord    <$>
  7.                           v .: "x" <*>
  8.                           v .: "y"
  9.     -- Usamos mzero para establecer un valor para el objeto de tipo incorrecto.
  10.     parseJSON _          = mzero


Se observa además el uso de la extensión de lenguaje OverloadedStrings, que permite que se escriba valores de texto como literales de cadena.

En el ejemplo se escribe manualmente la instancia FromJSON, pero se puede hacer de forma automática con:

  • Data.Aeson.TH que proporciona funciones de plantillas-haskell que obtendrán una instancia en tiempo de compilaión. La instancia generada está optimizada para el tipo de usuario.
  • Data.Aeson.Generic que proporciona la función fromJSON genérica que parsea cualquier tipo que es una instacia de Data.
  1. {-# LANGUAGE DeriveGeneric #-}
  2.  
  3. import GHC.Generics
  4.  
  5. data Coord { x :: Double, y :: Double } deriving Generic  
  6.  
  7. instance FromJSON Coord
  • Si el compilador soporta DeriveGeneric y extensiones DefaultSignatures, parseJSON podria tener una implementación generica por defecto.

De las tres opciones la mas óptima es la primera dado que la instancia esta optimizada para el propio tipo definido por el usuario.

Métodos

parseJSON :: Value -> Parser a

Instancias

  1. FromJSON Bool	 
  2. FromJSON Char	 
  3. FromJSON Double	 
  4. FromJSON Float	 
  5. FromJSON Int	 
  6. FromJSON Int8	 
  7. FromJSON Int16	 
  8. FromJSON Int32	 
  9. FromJSON Int64	 
  10. FromJSON Integer	 
  11. FromJSON Word	 
  12. FromJSON Word8	 
  13. FromJSON Word16	 
  14. FromJSON Word32	 
  15. FromJSON Word64	 
  16. FromJSON ()	 
  17. FromJSON ByteString	  
  18. FromJSON ByteString	 
  19. FromJSON Text	 
  20. FromJSON Number	 
  21. FromJSON Text	 
  22. FromJSON IntSet	 
  23. FromJSON ZonedTime	 
  24. FromJSON UTCTime	 
  25. FromJSON Value	 
  26. FromJSON DotNetTime	 
  27. FromJSON [Char]	 
  28. FromJSON a => FromJSON [a]	 
  29. FromJSON (Ratio Integer)	 
  30. FromJSON a => FromJSON (Maybe a)	 
  31. HasResolution a => FromJSON (Fixed a)	 
  32. FromJSON a => FromJSON (Dual a)	 
  33. FromJSON a => FromJSON (First a)	 
  34. FromJSON a => FromJSON (Last a)	 
  35. FromJSON a => FromJSON (IntMap a)	 
  36. (Ord a, FromJSON a) => FromJSON (Set a)	 
  37. (Eq a, Hashable a, FromJSON a) => FromJSON (HashSet a)	 
  38. FromJSON a => FromJSON (Vector a)	 
  39. (Vector Vector a, FromJSON a) => FromJSON (Vector a)	 
  40. (Storable a, FromJSON a) => FromJSON (Vector a)	 
  41. (Prim a, FromJSON a) => FromJSON (Vector a)	 
  42. (FromJSON a, FromJSON b) => FromJSON (Either a b)	 
  43. (FromJSON a, FromJSON b) => FromJSON (a, b)	 
  44. FromJSON v => FromJSON (Map String v)	 
  45. FromJSON v => FromJSON (Map ByteString v)	 
  46. FromJSON v => FromJSON (Map ByteString v)	 
  47. FromJSON v => FromJSON (Map Text v)	 
  48. FromJSON v => FromJSON (Map Text v)	 
  49. FromJSON v => FromJSON (HashMap String v)	 
  50. FromJSON v => FromJSON (HashMap ByteString v)	 
  51. FromJSON v => FromJSON (HashMap ByteString v)	 
  52. FromJSON v => FromJSON (HashMap Text v)	 
  53. FromJSON v => FromJSON (HashMap Text v)	 
  54. (FromJSON a, FromJSON b, FromJSON c) => FromJSON (a, b, c)	 
  55. (FromJSON a, FromJSON b, FromJSON c, FromJSON d) => FromJSON (a, b, c, d)

data Result a

Resultado de ejecutar el parser.

Constructor Devuelve a si la conversion es correcta o un mensaje en caso de error.

data Result a = Error String
            | Success a
              deriving (Eq, Show, Typeable)

Instancias

  1. Monad Result	 
  2. Functor Result	 
  3. Typeable1 Result	 
  4. MonadPlus Result	 
  5. Applicative Result	 
  6. Alternative Result	 
  7. Eq a => Eq (Result a)	 
  8. Show a => Show (Result a)	 
  9. Monoid (Result a)	 
  10. NFData a => NFData (Result a)

fromJSON

Convierte un valor desde JSON. Si los tipos no coinciden da error.

  1. fromJSON :: (FromJSON a) => Value -> Result a
  2. fromJSON = parse parseJSON

ToJSON

Funciona de la misma manera que la clase FromJSON pero a la inversa, es decir, identifica un tipo que puede ser convertido en un objeto JSON. Un ejemplo de instancia de esta clase seria:

  1. {-# LANGUAGE OverloadedStrings #-}
  2.  
  3. data Coord { x :: Double, y :: Double }
  4.  
  5. instance ToJSON Coord where
  6.    toJSON (Coord x y) = object ["x" .= x, "y" .= y]

De la misma manera se puede hacer la instanciación de forma automática:

  1. {-# LANGUAGE DeriveGeneric #-}
  2.  
  3. import GHC.Generics
  4.  
  5. data Coord { x :: Double, y :: Double } deriving Generic
  6.  
  7. instance ToJSON Coord

Métodos

  1. toJSON :: a -> Value

Instancias

  1. ToJSON Bool
  2. ToJSON Char
  3. ToJSON Bool
  4. ToJSON Double
  5. ToJSON Float	 
  6. ToJSON Int	 
  7. ToJSON Int8	 
  8. ToJSON Int16	 
  9. ToJSON Int32	 
  10. ToJSON Int64	 
  11. ToJSON Integer	 
  12. ToJSON Word	 
  13. ToJSON Word8	 
  14. ToJSON Word16	 
  15. ToJSON Word32	 
  16. ToJSON Word64	 
  17. ToJSON ()	 
  18. ToJSON ByteString	 
  19. ToJSON ByteString	 
  20. ToJSON Text	 
  21. ToJSON Number	 
  22. ToJSON Text	 
  23. ToJSON IntSet	 
  24. ToJSON ZonedTime	 
  25. ToJSON UTCTime	 
  26. ToJSON Value	 
  27. ToJSON DotNetTime	 
  28. ToJSON [Char]	 
  29. ToJSON a => ToJSON [a]	 
  30. ToJSON (Ratio Integer)	 
  31. ToJSON a => ToJSON (Maybe a)	 
  32. HasResolution a => ToJSON (Fixed a)	 
  33. ToJSON a => ToJSON (Dual a)	 
  34. ToJSON a => ToJSON (First a)	 
  35. ToJSON a => ToJSON (Last a)	 
  36. ToJSON a => ToJSON (IntMap a)	 
  37. ToJSON a => ToJSON (Set a)	 
  38. ToJSON a => ToJSON (HashSet a)	 
  39. ToJSON a => ToJSON (Vector a)	 
  40. (Vector Vector a, ToJSON a) => ToJSON (Vector a)	 
  41. (Storable a, ToJSON a) => ToJSON (Vector a)	 
  42. (Prim a, ToJSON a) => ToJSON (Vector a)	 
  43. (ToJSON a, ToJSON b) => ToJSON (Either a b)	 
  44. (ToJSON a, ToJSON b) => ToJSON (a, b)	 
  45. ToJSON v => ToJSON (Map String v)	 
  46. ToJSON v => ToJSON (Map ByteString v)	 
  47. ToJSON v => ToJSON (Map ByteString v)	 
  48. ToJSON v => ToJSON (Map Text v)	 
  49. ToJSON v => ToJSON (Map Text v)	 
  50. ToJSON v => ToJSON (HashMap String v)	 
  51. ToJSON v => ToJSON (HashMap ByteString v)	 
  52. ToJSON v => ToJSON (HashMap ByteString v)	 
  53. ToJSON v => ToJSON (HashMap Text v)	 
  54. ToJSON v => ToJSON (HashMap Text v)	 
  55. (ToJSON a, ToJSON b, ToJSON c) => ToJSON (a, b, c)	 
  56. (ToJSON a, ToJSON b, ToJSON c, ToJSON d) => ToJSON (a, b, c, d)ToJSON Double

Inspección de valores

Las siguientes funciones se usan para aplicar una determinada función a un objeto del tipo Value definido en librería AESON. Estas funciones comprueban que el elemento Value, sea del tipo esperado para la función, y en caso contrario, devuelve un error.

  1. withObject :: String -> (Object -> Parser a) -> Value -> Parser a
  2. withText :: String -> (Text -> Parser a) -> Value -> Parser a
  3. withArray :: String -> (Array -> Parser a) -> Value -> Parser a
  4. withNumber :: String -> (Number -> Parser a) -> Value -> Parser a
  5. withBool :: String -> (Bool -> Parser a) -> Value -> Parser a
  • withObject: recibe una función que tiene como argumento un Value de tipo Object, y un argumento de tipo Value. Si Value es de tipo Object, aplica la función, sinó, devuelve un error.
  • withText: recibe una función que tiene como argumento un Value de tipo Text, y un argumento de tipo Value. Si Value es de tipo Text, aplica la función, sinó, devuelve un error.
  • withArray: recibe una función que tiene como argumento un Value de tipo Array, y un argumento de tipo Value. Si Value es de tipo Array, aplica la función, sinó, devuelve un error.
  • withNumber: recibe una función que tiene como argumento un Value de tipo Number, y un argumento de tipo Value. Si Value es de tipo Number, aplica la función, sinó, devuelve un error.
  • withBool: recibe una función que tiene como argumento un Value de tipo Bool, y un argumento de tipo Value. Si Value es de tipo Bool, aplica la función, sinó, devuelve un error.

Un ejemplo de implementación de una de estas funciones, donde se pueda apreciar mejor como funcionan, es el siguiente:

  1. withObject :: String -> (Object -> Parser a) -> Value -> Parser a
  2. withObject _        f (Object obj) = f obj
  3. withObject expected _ v            = typeMismatch expected v

Constructores y descriptores de acceso

Para construir pares clave valor se utiliza:

  1. (.=) :: ToJSON a => Text -> a -> Pair

Para recuperar el valor asociado a un JSON se utilizará el elemento Object con la clave que se desea recuperar. Para esto podemos usar uno de los dos descriptores de acceso:

  • (.:) Se utilizará cuando en el objeto el par clave-valor son imprescindibles. En este caso si la clave no esta definida o si el valor no se puede convertir se devolverá empty (vacío).
  • (.?) Se utilizará cuando en el objeto el par clave-valor pueden ser opcionales. Para este caso si la clave no existe el resultado es Nothing y empty si el valor no se puede convertir.
  1. (.:) :: FromJSON a => Object -> Text -> Parser a
  2.  
  3. (.?) :: FromJSON a => Object -> Text -> Parser (Maybe a)


El siguiente ejemplo muestra el uso de estos descriptores:

  1. import Control.Applicative ((<$>), (<*>), empty)
  2. import Data.Aeson
  3. import qualified Data.ByteString.Lazy.Char8 as BL
  4.  
  5. data Coord = Coord { x :: Double, y :: Double }
  6.              deriving (Show)
  7.  
  8. --la siguiente instancia ToJSON permite codificar un valor a JSON.
  9. instance ToJSON Coord where
  10.   toJSON (Coord xV yV) = object [ "x" .= xV,
  11.                                   "y" .= yV ]a
  12.  
  13. -- La instancia FromJSON permitirá decodificar un valor desde JSON. El objeto debe 
  14. -- coincidir con el formato usado por la instancia ToJSON.
  15.  
  16. instance FromJSON Coord where
  17.     parseJSON (Object v) = Coord <$>
  18.                          v .: "x" <*>
  19.                          v .: "y"
  20.     parseJSON _          = empty

Como último descriptor tenemos (.!=) para proporcionar valores por defecto para los campos de los objetos JSON opcionales. Se usa en combinación con (.:?)

  1. (.!=) :: Parser (Maybe a) -> a -> Parser a

Como ejemplo de uso podemos ver los siguientes:

  1. v1 <- o .:? "opt_field_with_dfl" .!= "default_val"
  2. v2 <- o .:  "mandatory_field"
  3. v3 <- o .:? "opt_field2"

Ejemplos

Ejemplo completo

Este es un ejemplo sencillo. Código completo:

  1. {-# LANGUAGE OverloadedStrings #-}
  2.  
  3. module Main where
  4.  
  5. import Control.Applicative ((<$>), (<*>), empty)
  6. import Data.Aeson
  7. import qualified Data.ByteString.Lazy.Char8 as BL
  8.  
  9. data Coord = Coord { x :: Double, y :: Double } deriving Show
  10.  
  11. instance FromJSON Coord where
  12.   parseJSON (Object v) = Coord <$>
  13.                          v .: "x" <*>
  14.                          v .: "y"
  15.   parseJSON _ = empty
  16.  
  17. instance ToJSON Coord where
  18.   toJSON (Coord x y) = object ["x" .= x, "y" .= y]
  19.  
  20. main :: IO ()
  21. main = do
  22.   print (Coord 10 10)
  23.   print (encode (Coord 10 10))
  24.   BL.putStrLn (encode (Coord 10 10))
  25.   print (decode (encode (Coord 10 10)) :: Maybe Coord)

Este código define una colección de datos llamada Coord, que tiene dos datos Doubles llamados x e y. Se instancian clases para su conversión desde y hacia JSON y se hace un ejemplo en el main que muestra en la salida estándar resutlados. Veamos el código paso a paso.

  1. import Control.Applicative ((<$>), (<*>), empty)
  2. import Data.Aeson
  3. import qualified Data.ByteString.Lazy.Char8 as BL

Aquí se importan los paquetes que vamos a usar en el ejemplo. Es necesario importar BL puesto que Aeson no traduce los objetos JSON a String directamente.

  1. data Coord = Coord { x :: Double, y :: Double } deriving Show

Creamos la colección Coord.

  1. instance FromJSON Coord where
  2.   parseJSON (Object v) = Coord <$>
  3.                          v .: "x" <*>
  4.                          v .: "y"
  5.   parseJSON _ = empty

Instanciamos FromJSON para Coord. Aquí se indica el esquema de traducción desde JSON hasta Coord.

  1. instance ToJSON Coord where
  2.   toJSON (Coord x y) = object ["x" .= x, "y" .= y]

Ahora se instancia ToJSON para Coord. Se define el esquema de traducción desde Coord hasta JSON.

  1.   print (Coord 10 10)
  2.   print (encode (Coord 10 10))
  3.   BL.putStrLn (encode (Coord 10 10))
  4.   print (decode (encode (Coord 10 10)) :: Maybe Coord)

Este es el código que hace uso de toda la maquinaria de Aeson. Descripción línea a línea:

  • En la línea 22 creamos una Coord y la imprimimos para verla.
  • En la línea 23 la transformamos a JSON.
  • En la línea 24 transformamos dicho JSON a string (básicamente cambiarle codificación)
  • En la línea 25 decodificamos el string con el contenido JSON, sugiriéndole que es un Coord.

Ejemplo con plantillas haskell

Este es un ejemplo usando las plantillas de Haskell. Código completo:

  1. {-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
  2.  
  3. module Main where
  4.  
  5. import Data.Aeson (decode, encode)
  6. import Data.Aeson.TH (deriveJSON)
  7. import qualified Data.ByteString.Lazy.Char8 as BL
  8.  
  9. data Coord = Coord { x :: Double, y :: Double }
  10.              deriving (Show)
  11.  
  12. $(deriveJSON id '' Coord)
  13.  
  14. main :: IO ()
  15. main = do
  16.   print (Coord 10 10)
  17.   print (encode (Coord 10 10))
  18.   BL.putStrLn (encode (Coord 10 10))
  19.   print (decode (encode (Coord 10 10)) :: Maybe Coord)

En este caso no creamos instancias de FromJSON y ToJSON, hacemos que haskell lo haga por nosotros. Se hace en la línea 12.

  1. $(deriveJSON id '' Coord)

El parámetro al que pasamos 'id' es para una función que haga una transformación de los nombres de los campos. Usando la función id se usan tal cual están definidos en la colección.

Ejemplo con tipos de datos Genéricos

Ahora vamos a ver un ejemplo usando datos genéricos. Código completo:

  1. {-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
  2.  
  3. import Data.Aeson 
  4. import qualified Data.ByteString.Lazy.Char8 as BL
  5. import GHC.Generics (Generic)
  6.  
  7. data Coord = Coord { x :: Double, y :: Double }
  8.              deriving (Show, Generic)
  9.  
  10. instance FromJSON Coord
  11. instance ToJSON Coord
  12.  
  13. main :: IO ()
  14. main = do
  15.   print (Coord 10 10)
  16.   print (encode (Coord 10 10))
  17.   BL.putStrLn (encode (Coord 10 10))
  18.   print (decode (encode (Coord 10 10)) :: Maybe Coord)

Veamos los cambios sobre código de otros ejemplos.

  1. import GHC.Generics (Generic)

Importamos un paquete más en la línea 5.

  1. data Coord = Coord { x :: Double, y :: Double }
  2.              deriving (Show, Generic)

En la definición de la colección, debemos derivar también desde Generic.

  1. instance FromJSON Coord
  2. instance ToJSON Coord

Declaramos instancias de FromJSON y ToJSON, pero no definimos los métodos. Haskell lo hará por nosotros al derivar desde Generic.

Referencias

Herramientas personales