Convert JSON with state in circus

Note. I am copying this question from the circe Gitter channel for posterity.

Suppose we want to translate this JSON document:

{ "places": [{ "id": "dadcc0d9-0615-4e46-9df4-2619f49930a0" }, { "id": "21d02f4b-7e88-47d7-bf2b-48e50761b6c3" }], "transitions": [{ "id": "10a3aee5-541b-4d04-bb45-cb1dbbfe2128", "startPlaceId": "dadcc0d9-0615-4e46-9df4-2619f49930a0", "endPlaceId": "21d02f4b-7e88-47d7-bf2b-48e50761b6c3" }], "routes": [{ "id": "6ded1763-86c0-44ce-b94b-f05934976a3b", "transitionId": "10a3aee5-541b-4d04-bb45-cb1dbbfe2128" }] } 

In it:

 { "places": [{ "id": "1" }, { "id": "2" }], "transitions": [{ "id": "3", "startPlaceId": "ref:1", "endPlaceId": "ref:2" }], "routes": [{ "id": "4", "transitionId": "ref:3" }] } 

Ie, we want to replace the UUID in each id simple incremental numeric identifier and replace all links to each UUID with links to the new identifiers.

How can we do this with circe ?

+5
source share
1 answer

This can be done relatively directly with the state monad transformer from Cats (from the library, which depends on the cycle):

 import cats.data.StateT import cats.std.option._ import cats.std.list._ import cats.syntax.traverse._ import io.circe.{ Json, JsonObject } import java.util.UUID def update(j: Json): StateT[Option, Map[UUID, Long], Json] = j.arrayOrObject( StateT.pure[Option, Map[UUID, Long], Json](j), _.traverseU(update).map(Json.fromValues), _.toList.traverseU { case ("id", value) => StateT { (ids: Map[UUID, Long]) => value.as[UUID].toOption.map { uuid => val next = if (ids.isEmpty) 1L else ids.values.max + 1L (ids.updated(uuid, next), "id" -> Json.fromString(s"$next")) } } case (other, value) => value.as[UUID].toOption match { case Some(uuid) => StateT { (ids: Map[UUID, Long]) => ids.get(uuid).map(id => (ids, other -> Json.fromString(s"ref:$id"))) } case None => update(value).map(other -> _) } }.map(Json.fromFields) ) 

And then:

 import io.circe.literal._ val doc: Json = json""" { "places": [{ "id": "dadcc0d9-0615-4e46-9df4-2619f49930a0" }, { "id": "21d02f4b-7e88-47d7-bf2b-48e50761b6c3" }], "transitions": [{ "id": "10a3aee5-541b-4d04-bb45-cb1dbbfe2128", "startPlaceId": "dadcc0d9-0615-4e46-9df4-2619f49930a0", "endPlaceId": "21d02f4b-7e88-47d7-bf2b-48e50761b6c3" }], "routes": [{ "id": "6ded1763-86c0-44ce-b94b-f05934976a3b", "transitionId": "10a3aee5-541b-4d04-bb45-cb1dbbfe2128" }] } """ 

And finally:

 scala> import cats.std.long._ import cats.std.long._ scala> import cats.std.map._ import cats.std.map._ scala> update(doc).runEmptyA res0: Option[io.circe.Json] = Some({ "places" : [ { "id" : "1" }, { "id" : "2" } ], "transitions" : [ { "id" : "3", "startPlaceId" : "ref:1", "endPlaceId" : "ref:2" } ], "routes" : [ { "id" : "4", "transitionId" : "ref:3" } ] }) 

If any id field is not a legal UUID, or if any other field contains a link to an unknown UUID, the calculation will end with None . This behavior could be clarified as necessary, and if you need more specific information about where the error occurred, you can adapt the implementation to work with cursors instead of JSON values ​​(but this has become a bit more complicated).

+8
source

Source: https://habr.com/ru/post/1247064/


All Articles