2016-04-13 7 views
5

Nota: sto copiando questa domanda dal circe Gitter channel per motivi di posteri.Trasformazione di JSON con stato in cir.

supponiamo di voler tradurre questo documento 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" 
    }] 
} 

In questa:

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

Vale a dire, vogliamo sostituire l'UUID in ogni id con un semplice identificativo numerico incrementato, e per sostituire tutti i riferimenti a ciascun UUID con riferimenti ai nuovi identificatori.

Come possiamo fare questo con circe?

risposta

8

È possibile realizzare questo relativamente semplicemente con il trasformatore monade stato dal Cats (una biblioteca che circe dipende):

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) 
) 

Poi:

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" 
    }] 
} 
""" 

Infine:

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" 
    } 
    ] 
}) 

Se un campo id non è un UUID legittimo o se qualsiasi altro campo contiene un riferimento a un UUID sconosciuto, il calcolo avrà esito negativo con None. Questo comportamento potrebbe essere leggermente ridefinito se necessario e, se si desidera ottenere informazioni più specifiche su dove si è verificato l'errore, è possibile adattare l'implementazione in modo che funzioni con i cursori anziché con i valori JSON (ma ciò risulterebbe un po 'più complesso).

+0

Può 'ids.updated (uuid, next)' essere sostituito con 'ids + (uuid -> next)'? –

+1

@ Łukasz Sì, ma trovo 'aggiornato' un po 'più chiaro, dal momento che non richiede la creazione esplicita di una tupla, e dal momento che ci sarà sempre solo una coppia chiave-valore che stiamo aggiungendo lì. –

+0

Va bene, grazie per la spiegazione. Trovo un po 'di confusione in quanto ciò consentirebbe anche di sostituire il valore esistente ma ciò non accadrà mai e la tua intenzione è quella di inserire un nuovo elemento, ma ora posso anche vedere i vantaggi del tuo approccio. –

Problemi correlati