En la entrada anterior, Circe I: introducción y parseadores, realicé una introducción de la librería Circe y, además, describí como se realiza un parseo de una estructura JSON. En la presente entrada, Circe II: manipulación y modificación de JSON, describiré cómo se puede manipular una estructura JSON.
El sketchnote de la presente entrada, queda descrito en la siguiente imagen:
Las operaciones que trataré en la entrada son para la obtención de un campo determinado, o bien, para realizar una modificación de un campo. La importación de los elementos necesarios para manipular JSON son los siguientes:
import cats.syntax.either._ import io.circe._ import io.circe.parser._
La estructura JSON para las pruebas es la siguiente:
val json: String = """ { "id": "c730433b-082c-4984-9d66-855c243266f0", "name": "Foo", "counts": [1, 2, 3], "values": { "bar": true, "baz": 100.001, "qux": ["a", "b", "c"] } } """
Para manipular una estructura JSON, es necesario aplicar el parseador para determinar si está bien formado; para ello, realizamos los pasos definidos en la anterior entrada. Para nuestro ejemplo, el parseo se realiza de la siguiente forma:
val doc: Json = parse(json).getOrElse(Json.Null)
Acceso a datos
Para acceder a los datos existentes en un JSON es necesario definir un cursor a partir del objeto obtenido del parseo de la estructura JSON. La definición del cursor en nuestro ejemplo es el siguiente:
val cursor: HCursor = doc.hcursor
Una vez creado el cursor, estamos en disposición para acceder a los datos, existiendo dos formas de acceso, las cuales son las siguientes:
- La primera forma para acceder a un campo utilizamos la función downField(«NombreCampo»), para obtener del cursor el elemento referenciado para todos los campos; y, una vez posicionados en el campo seleccionado, utilizamos la función as especificando el tipo. Un ejemplo de esta forma de acceso es la siguiente:
val valueBaz1: Decoder.Result[Double] = cursor.downField("values").downField("baz").as[Double] println(s"[*] Valor 'baz' del JSON forma 1=${valueBaz1}") println
La salida por consola es la siguiente:
[*] Valor 'baz' del JSON forma 1=Right(100.001)
- La segunda forma para acceder a un campo utilizamos la función downField(«NombreCampo»), para obtener el cursor al elemento referenciado; y, con ésta referencia, utilizamos la función get especificando el tipo y el nombre del campo. Un ejemplo de esta forma de acceso es la siguiente:
val valueBaz2: Decoder.Result[Double] = cursor.downField("values").get[Double]("baz") println(s"[*] Valor 'baz' del JSON forma 2=${valueBaz1}") println
La salida por consola es la siguiente:
[*] Valor 'baz' del JSON forma 2=Right(100.001)
- Para acceder a una lista de elementos utilizamos la función downField(«NombreCampo») y, además, la función downArray junto a la
función as con el tipo. Un ejemplo de acceso al primer elemento, último elemento y acceso al elemento colocado a la derecha del activo es el siguiente:
val secondQux1: Decoder.Result[String] = cursor.downField("values").downField("qux").downArray.right.as[String] println(s"[*] Valor del array del campo 'qux' del JSON =${secondQux1}") println val firstQux: Decoder.Result[String] = cursor.downField("values").downField("qux").downArray.first.as[String] println(s"[*] Valor del array del campo 'qux' del JSON =${firstQux}") println val lastQux: Decoder.Result[String] = cursor.downField("values").downField("qux").downArray.last.as[String] println(s"[*] Valor del array del campo 'qux' del JSON =${lastQux}") println
La salida por consola es la siguiente:
[*] Valor del array del campo 'qux' del JSON =Right(b) [*] Valor del array del campo 'qux' del JSON =Right(a) [*] Valor del array del campo 'qux' del JSON =Right(c)
Modificación de datos
Para realizar la modificación de un campo es necesario realizar funciones parecidas al acceso de datos. Es necesario utilizar la función downField y las función withFocus. Así, unos ejemplos de modificación son los siguientes:
- Modificación del campo name de la estructura JSON de ejemplo asignando su valor del revés y su posterior visualización, se realiza de la siguiente forma:
val reversedNameCursor: ACursor = cursor.downField("name").withFocus(_.mapString(_.reverse)) val reversedName: Option[Json] = reversedNameCursor.top // Retorna todo el JSON. println(s"[-] Operación Reverse del JSON =${reversedName}") println
La salida por consola es la siguiente:
[-] Operación Reverse del JSON =Some({ "id" : "c730433b-082c-4984-9d66-855c243266f0", "name" : "ooF", "counts" : [ 1, 2, 3 ], "values" : { "bar" : true, "baz" : 100.001, "qux" : [ "a", "b", "c" ] } })
- La asignación del valor «VALOR MODIFICADO» al campo name y su posterior visualización, se realiza de la siguiente forma:
val modificacion1ameCursor: ACursor = cursor.downField("name").withFocus(_.mapString( elem => "VALOR MODIFICADO")) println(s"[-] Modificación del campo name del JSON =${modificacion1ameCursor.top}") println
La salida por consola es la siguiente:
[-] Modificación del campo name del JSON =Some({ "id" : "c730433b-082c-4984-9d66-855c243266f0", "name" : "VALOR MODIFICADO", "counts" : [ 1, 2, 3 ], "values" : { "bar" : true, "baz" : 100.001, "qux" : [ "a", "b", "c" ] } })
En la siguiente entrada, Circe III: encoding y decoding, realizaré una descripción de cómo realizar operaciones de codificación y decodificación de estructuras JSON con Circe.