Circe: Konvertieren von Karte nicht möglich[_, Any]

Erstellt am 2. März 2016  ·  6Kommentare  ·  Quelle: circe/circe

Beim Ausprobieren der Bibliothek habe ich festgestellt, dass es keine impliziten Konvertierungen für Any oder AnyVal gibt.

Betrachten Sie beispielsweise den folgenden Code:

case class Container(m: Map[String, AnyVal])
val container = Container(Map("name" -> "John".asInstanceOf[AnyVal], "age" -> 50))
container.asJson

Dies wird nicht funktionieren, da es keine implizite Zuordnung für ein AnyVal (oder für Any) gibt.

Ich habe versucht zu verstehen, wie man in solchen Fällen eigene implizite Konvertierungsrichtlinien für die Konvertierung von Objekten in JSON und umgekehrt schreiben kann.

Das Endziel hier ist etwa die Möglichkeit, zwischen jedem beliebigen JSON in eine Map[String, Any] und umgekehrt konvertieren zu können.

Hinweis: Wenn eine solche Funktionalität bereits existiert, weisen Sie mich bitte auf die richtige Richtung. Das konnte ich weder in der Dokumentation noch sonstwie finden.

Hilfreichster Kommentar

@robsonpeixoto Die Either Konstruktoren sind Fallklassen , also erzeugt generic Instanzen für sie wie für alle anderen Fallklassen (es gibt keine automatisch bereitgestellten Either Instanzen, und das ist es auch nicht Sonderfall).

Wenn Sie eine Darstellung ohne Tags wünschen, können Sie Folgendes tun:

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

implicit val encodeIntOrString: Encoder[Either[Int, String]] =
  Encoder.instance(_.fold(_.asJson, _.asJson))

implicit val decodeIntOrString: Decoder[Either[Int, String]] =
  Decoder[Int].map(Left(_)).or(Decoder[String].map(Right(_)))

Und dann:

scala> println(m1.asJson)
{
  "name" : "John",
  "age" : 50
}

scala> decode[Message](m1.asJson.noSpaces)
res1: cats.data.Xor[io.circe.Error,Message] = Right(Map(name -> Right(John), age -> Left(50)))

Funktioniert das für dich?

Alle 6 Kommentare

@ChetanBhasin circe basiert auf der Idee, dass Typen die Serialisierung vorantreiben – Sie fragen beispielsweise nach einer JSON-Darstellung eines Werts vom Typ Map[String, User] , und die entsprechenden Encoder werden zur Kompilierzeit ausgewählt – es fallen keine Kosten an oder Reflexionsgefahr zur Laufzeit. circe ist völlig hilflos, wenn es um Any oder AnyVal , wo Sie mit einem Wert _außer_ nichts Vernünftiges tun können, um ihn zur Laufzeit zu reflektieren (wenn das als vernünftig gilt).

Dies ist beabsichtigt und hat zwei Ziele: die Bibliothek einfach und sicher zu halten, indem jegliche Verwendung von Laufzeitreflexionen vermieden wird, und die Verwendung von Typen zu fördern. In vielerlei Hinsicht ist Map[String, Any] das Gegenteil von typsicherer funktionaler Programmierung, und circe zielt darauf ab, es sowohl möglich als auch wünschenswert zu machen, zu vermeiden, dass Typen wie Map[String, Any] irgendwo in Ihrem Programm auftauchen.

Natürlich könnten Sie Ihre eigenen Encoder und Decoder Instanzen für Typen wie Map[String, Any] schreiben, aber sie wären unsicher, unidiomatisch und möglicherweise weniger performant (wegen der Notwendigkeit von Laufzeitreflexion), so dass circe selbst mit ziemlicher Sicherheit nie so etwas von der Stange liefern wird.

Ich würde empfehlen, zu versuchen, Map[String, Any] vollständig zu vermeiden, aber wenn Sie dabei nicht weiterkommen und wirklich circe verwenden möchten (im Gegensatz zu einer JSON-Bibliothek, die die Laufzeitreflexion umfasst, was die meisten von ihnen sind :smile: ), könnten Sie zu Guss versuchen und Ihre konvertieren Map[String, Any] Werte in etwas mehr typ voll, wie zB Map[String, Either[Int, String]] , die Circe werden glücklich Codierung und Decodierung.

@travisbrown Danke für die Antwort.

Ich kann völlig verstehen, warum wir keine Konvertierungen zwischen Any- und JSON-Werten durchführen möchten und warum benutzerdefinierte Encoder eine schlechte Idee sind.

Ich denke jedoch, dass es sinnvoll ist, einen Datentyp zu haben, der nur mit JSON-Primitiven umgeht. Während die einzige Möglichkeit, ein JSON-Objekt aus Schlüssel-Wert-Paaren in Vanilla Scala darzustellen, beispielsweise darin besteht, Map[String, Any] oder Map[String, AnyVal] , könnten wir etwas wie Map[String, JsType] .

JsType in dieser Hinsicht eines der JSON-Primitive sein (dh String, Long, Boolean, Object, List oder null). Es wäre jedoch sinnvoll, zwischen JSON-Primitiven implizit nach Scala konvertieren zu können, während Rückwärts mithilfe eines JSON-Typs erreicht werden kann. In diesem Fall wäre es natürlich wunderbar, einige grundlegende Operationen an diesen Werten durchführen zu können (entweder durch implizite Konvertierung oder Implementierung von Methoden).

Dieser Ansatz wäre typsicher. Jetzt ist meine einzige Frage, ob es so etwas schon in der Bibliothek gibt?
Mir ist klar, dass dies ziemlich einfach zu implementieren ist, aber es wäre großartig, wenn wir es als Funktion aufnehmen könnten.

Es tut mir leid. Ich habe in der Bibliothek herumgefummelt und verstehe das besser. Werde das Thema schließen.

@travisbrown Ich habe Map[String, Either[Int, String]] getestet, aber es hat einen Json mit den Schlüsseln Left und Right :

import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
import cats.data.Xor

type Message = Map[String, Either[Int, String]]
object Message {
  def apply(elems: (String, Either[Int, String])*): Map[String, Either[Int, String]] = Map(elems: _*)
}
val m1 = Message("name" -> Right("John"), "age" -> Left(50))
println(m1.asJson)

// Exiting paste mode, now interpreting.

{
  "name" : {
    "Right" : {
      "b" : "John"
    }
  },
  "age" : {
    "Left" : {
      "a" : 50
    }
  }
}

Und wenn ich einen Json ohne diese Schlüssel konvertieren soll, funktioniert die Dekodierung nicht:

// ...
val json: String = """
  {
    "id": 1,
    "name": "Foo",
    "city": "NYC",
    "age": 10,
    "height": 175
  }
"""
val m2 = decode[Message](json)

// Exiting paste mode, now interpreting.
m2: cats.data.Xor[io.circe.Error,Message] = Left(DecodingFailure(CNil, List(El(DownField(id),true,false))))

Ist das das erwartete Verhalten?

@robsonpeixoto Die Either Konstruktoren sind Fallklassen , also erzeugt generic Instanzen für sie wie für alle anderen Fallklassen (es gibt keine automatisch bereitgestellten Either Instanzen, und das ist es auch nicht Sonderfall).

Wenn Sie eine Darstellung ohne Tags wünschen, können Sie Folgendes tun:

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

implicit val encodeIntOrString: Encoder[Either[Int, String]] =
  Encoder.instance(_.fold(_.asJson, _.asJson))

implicit val decodeIntOrString: Decoder[Either[Int, String]] =
  Decoder[Int].map(Left(_)).or(Decoder[String].map(Right(_)))

Und dann:

scala> println(m1.asJson)
{
  "name" : "John",
  "age" : 50
}

scala> decode[Message](m1.asJson.noSpaces)
res1: cats.data.Xor[io.circe.Error,Message] = Right(Map(name -> Right(John), age -> Left(50)))

Funktioniert das für dich?

Sehr gut, @travisbrown. Danke

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen