2016-04-13 7 views
5

Hinweis: Ich kopiere diese Frage aus der circe Gitter channel aus Gründen der Nachwelt.Umwandlung von JSON mit Zustand in circe

Angenommen, wir wollen dieses JSON-Dokument übersetzen:

{ 
    "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 diese:

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

Dh wir die UUID in jedem id mit einem einfachen inkrementierten numerischen Kennung ersetzt werden soll, und Ersetzen Sie alle Verweise auf jede UUID durch Verweise auf die neuen IDs.

Wie können wir das mit circe?

Antwort

8

Es ist möglich, diese relativ unkompliziert mit dem Zustand Monade Transformator zu erreichen von Cats (eine Bibliothek, die Circe ist abhängig von):

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

Und dann:

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

Und schließlich:

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

Wenn ein id Feld ist nicht ein Legitimierte UUID, oder wenn ein anderes Feld einen Verweis auf eine unbekannte UUID enthält, schlägt die Berechnung mit None fehl. Dieses Verhalten könnte bei Bedarf etwas verfeinert werden. Wenn Sie genauere Informationen über den Fehlerort wünschen, können Sie die Implementierung so anpassen, dass sie mit Cursorn anstelle von JSON-Werten arbeitet (dies würde jedoch etwas komplexer werden).

+0

Kann 'ids.updated (uuid, next)' durch 'ids + (uuid -> next)' ersetzt werden? –

+1

@ Łukasz Ja, aber ich finde 'aktualisiert' ein wenig klarer, da es nicht die explizite Erstellung eines Tupels erfordert und da es immer nur ein Schlüssel-Wert-Paar gibt, das wir dort hinzufügen. –

+0

Alles klar, danke für die Erklärung. Ich finde es ein wenig verwirrend, da dies auch erlauben würde, bestehenden Wert zu ersetzen, aber das wird nie passieren und Sie beabsichtigen ein neues Element einzufügen, aber jetzt kann ich auch Vorteile Ihres Ansatzes sehen. –

Verwandte Themen