2016-09-03 4 views
9

Ich muss eine aktualisierte Instanz aus einer Fallklasseninstanz erstellen (mit allen erforderlichen s implizit abgeleitet), mit einem unvollständigen JSON (einige Felder fehlen). Wie kann dies mit Argonaut (vorzugsweise) oder Circe (wenn ich muss) erreicht werden?Fallklasse von unvollständigem JSON mit Argonaut oder Circe aktualisieren

Beispiel:

case class Person(name:String, age:Int) 
val person = Person("mr complete", 42) 
val incompletePersonJson = """{"name":"mr updated"}""" 
val updatedPerson = updateCaseClassFromIncompleteJson(person, incompletePersonJson) 

println(updatedPerson) 
//yields Person(mr updated, 42) 

Ich bin mir ziemlich sicher, ich habe die json zu json AST zu analysieren, dann wandeln sie LabelledGeneric formlos, dann Shapeless Update verwenden irgendwie den Fall Klasseninstanz zu aktualisieren.


bearbeiten 2

Nach der Shapeless Quelle Liest ich, dass ich finden kann meinen eigenen "Default" -Objekt erzeugen. Es ist mir gelungen, eine Lösung zu erstellen, bei der die Instanz der Fallklasse beim Parsen des Jsons vorhanden sein muss. Ich hatte gehofft, dies zu vermeiden und stattdessen die Instanz später zur Verfügung zu stellen. Jedenfalls ist es hier:

import shapeless._ 
import argonaut._ 
import ArgonautShapeless._ 
import shapeless.ops.hlist.Mapper 

case class Person(name: String, age: Int) 

object MkDefault { 

    object toSome extends Poly1 { 
    implicit def default[P] = at[P](Some(_)) 
    } 

    def apply[P, L <: HList, D <: HList] 
    (p: P) 
    (implicit 
    g: Generic.Aux[P, L], 
    mpr: Mapper.Aux[toSome.type, L, D] 
): Default.Aux[P, mpr.Out] = 
    Default.mkDefault[P, D](mpr(g.to(p))) 
} 


object Testy extends App { 
    implicit val defs0 = MkDefault(Person("new name? NO", 42)) 
    implicit def pd = DecodeJson.of[Person] 
    val i = """{"name":"Old Name Kept"}""" 
    val pp = Parse.decodeOption[Person](i).get 
    println(pp) 
} 

Dies ergibt Person(Old Name Kept,42).

+0

Debug ArgonautShapeless 'DecodeJson Schlussfolgerung (ArgonautShapeless.derivedDecodeJson), ich sehe, dass ein Objekt defaults = Defaults $ AsOptions $$ anon $ 9 wird mit Werten None :: None :: HNil instanziiert. Für mich scheint es, dass, wenn ich das irgendwie durch eine implizite Instanz ersetzen könnte, die ich mir selbst anbiete, ich Standardeinstellungen machen könnte, um den fehlenden JSON irgendwie zu füllen. – eirirlar

+1

Das einzige, was ich mir vorstellen kann, dass es in einer typsicheren Weise tun würde, ist, Ihre vorhandene Fallklasse in json umzuwandeln, beide in eine Map [String, Any] umzuwandeln, dann die Maps zusammenzuführen, zurück in json zu konvertieren. dann reparse – Falmarri

+0

@Falmarri das ist eigentlich keine so schlechte Idee, ich werde es als Backup-Lösung im Auge behalten, da es natürlich mehr Zeit und Ressourcen vom Computer erfordern würde. – eirirlar

Antwort

14

Aus Gründen der Vollständigkeit halber: Unterstützung für "Patching" Instanzen wie dies in circe seit dem 0.2-Release zur Verfügung gestellt worden:

import io.circe.jawn.decode, io.circe.generic.auto._ 

case class Person(name: String, age: Int) 

val person = Person("mr complete", 42) 
val incompletePersonJson = """{"name":"mr updated"}""" 

val update = decode[Person => Person](incompletePersonJson) 

Und dann:

scala> println(update.map(_(person))) 
Right(Person(mr updated,42)) 

Meine ursprüngliche blog post darüber Die Technik verwendet Argonaut (meistens seit ich es ein paar Monate geschrieben habe, bevor ich anfing, an circe zu arbeiten), und diese Implementierung ist available as a library, obwohl ich sie nie irgendwo veröffentlicht habe.

+1

Genau das habe ich gesucht. In meinen Projekten jetzt von Argonaut zu Circe wechseln. Tolle Arbeit, weiter so: D – eirirlar

+0

Bemerkenswerter Unterschied zu Argonaut nach dem Zurückgehen von HTTP-PATCH zu HTTP-POST-Testen mit Circe: Standardwerte von Fallklassen werden in Circe vollständig ignoriert, während in Argonaut ein Feld vom JSON fehlt und hatte einen Standardwert, würde es ok dekodieren. In Circe ist auch ein offenes Thema aufgetaucht: https://github.com/travisbrown/circe/issues/65 Frage mich, ob es in naher Zukunft für eine Veröffentlichung geplant ist? – eirirlar

3

Sie können diejenigen implicit val defs/pd mit Makro-Annotation auf Person (in object Person, zum Beispiel, und import Person._ tun implicits rufen) erzeugen. Siehe this unvollendete Simulacrum in scalameta (scala-reflect ist auch in Ordnung, aber scheint, als könnte scalameta hier genug sein) für Anwendungsbeispiele. Außerdem müssen Sie den fehlenden Standardwert (42) irgendwo angeben, zum Beispiel, falls der Klassenkonstruktor (age: Int = 42, Erkennung auch im Makro möglich ist).

+0

Danke dafür. Ich wollte vermeiden, meine eigenen Makros zu erstellen, da sie normalerweise in die Maintenance-Hölle übergehen, aber ich habe Scalameta oder Simulacrum noch nicht betrachtet. Ich werde das Kopfgeld heute offen halten, für den Fall, dass jemand eine nicht-benutzerdefinierte, makro-formlose Lösung für einen Kreis oder Argonaut postet. Wenn keine Antworten reinkommen, gebe ich Ihnen einfach die 50 für die Mühe :) – eirirlar

+0

Ich arbeite an ausführlicher Anleitung zu Scalameta-basierten Makro-Annotationen. Hoffe, es in ~ Woche zu beenden."simulacrum-meta" ist nur ein Beispiel für Scalametas APIs. Im Allgemeinen sollte scalameta gegenüber scala-reflect bevorzugt werden, wenn es die Anforderungen erfüllt (z. B. wird die semantische API noch nicht unterstützt). In diesem Fall denke ich, dass Scalameta den Job machen sollte. – dveim

Verwandte Themen