Finalizo la serie de Circe con la presente entrada, Circe IV: ópticas, en la cual me centraré en cómo Circe emplea componentes ópticos para facilitar su funcionalidad. Circe no implementa ópticas, sino que se ayuda de la librería Monocle para implementar esta funcionalidad.
El sketchnote de la presente entrada, queda descrito en la siguiente imagen:
Para el lector interesado en la librería óptica Monocle, puede acceder a los siguientes enlaces de la librería Monocle que tengo publicados:
- Monocle I: introducción y lente Iso
- Monocle II: lente Lens
- Monocle III: lente Optional
- Monocle IV: lente Prism
- Monocle V: lente Traversal
El primer paso a realizar es definir una estructura JSON de prueba con lo cual realizar la comparativa entre el modo de trabajo sin ópticas y con ópticas. El JSON de pruebas es el siguiente:
import cats.syntax.either._ import io.circe._ import io.circe.parser._ val json: Json = parse( """ { "order": { "customer": { "name": "Custy McCustomer", "contactDetails": { "address": "1 Fake Street, London, England", "phone": "0123-456-789" } }, "items": [{ "id": 123, "description": "banana", "quantity": 10 }, { "id": 456, "description": "apple", "quantity": 20 }], "total": 123.45 } } """).getOrElse(Json.Null)
Acceso a datos en Circe sin ópticas
Como he descrito en las entradas anteriores al tema, el acceso a los datos se realiza con un cursor, la función downField y get entre otros. Para refrescar los conceptos, en el siguiente snippet se definen dos ejemplos: el primero, acceso al campo Phone del JSON de prueba; y, el segundo, acceso a los valores de un array del JSON de prueba. El código es el siguiente:
println(s"[*] Número de teléfono del cliente(NO ÓPTICA): ${json.hcursor.downField("order") .downField("customer") .downField("contactDetails") .get[String]("phone").toOption}") val items: Vector[Json] = json.hcursor.downField("order").downField("items"). focus.flatMap(_.asArray). getOrElse(Vector.empty) val quantities: Vector[Int] = items.flatMap( _.hcursor.get[Int]("quantity").toOption ) println(s"[*] Obtención de un Array del JSON=${quantities} ")
La salida por consola es la siguiente:
[*] Número de teléfono del cliente(NO ÓPTICA): Some(0123-456-789) [*] Obtención de un Array del JSON=Vector(10, 20)
Acceso a datos en Circe con ópticas
La utilización de ópticas supone la definición de un regla de acceso para cada campo. Así, tenemos que definir para cada campo una óptica. Para los ejemplos del apartado anterior, definimos las ópticas para el campo phone y el array quantity de la siguiente forma:
import io.circe.optics.JsonPath._ val _phoneNum = root.order.customer.contactDetails.phone.string println(s"Número de teléfono del cliente (ÓPTICA): ${_phoneNum.getOption(json)}") val items: List[Int] = root.order.items.each.quantity.int.getAll(json) println(s"Número de teléfono del cliente (ÓPTICA): ${items}")
La salida por consola es la siguiente:
Número de teléfono del cliente (ÓPTICA): Some(0123-456-789) Número de teléfono del cliente (ÓPTICA): List(10, 20)
Modificación de datos en Circe con ópticas
Para realizar la moficación de un campo, se emplea la función modify de la óptica de aquel campo a modificar. Para realizar la modificación del campo quantity, se realiza de la siguiente forma:
import io.circe.optics.JsonPath._ import io.circe._ val doubleQuantities: Json => Json = root.order.items.each.quantity.int.modify(_ * 2) println(s"Multiplicación del campo quantity x2= ${doubleQuantities(json)} ")
La salida por consola es la siguiente:
Multiplicación del campo quantity x2= { "order" : { "customer" : { "name" : "Custy McCustomer", "contactDetails" : { "address" : "1 Fake Street, London, England", "phone" : "0123-456-789" } }, "items" : [ { "id" : 123, "description" : "banana", "quantity" : 20 }, { "id" : 456, "description" : "apple", "quantity" : 40 } ], "total" : 123.45 } }
Llegado a este punto, podemos llegar a la conclusión final que la operativa con estructuras JSON con la librería Circe es una tarea sencilla y de fácil aprendizaje: el acceso, modificación y transformación de JSON a una case class o viceversa, son tareas con una pequeña complejidad. Además, si se conoce el funcionamiento de las librerías ópticas, el acceso y manipulación de JSON es más sencillo aún.
Para el lector interesado, el conjunto de las entradas de la librería Circe son las siguientes: