java json scala playframework playframework-2.0

java - Scala/Play: analizar JSON en el mapa en lugar de JsObject



playframework playframework-2.0 (5)

He elegido utilizar el módulo de Jackson para Scala .

import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper val mapper = new ObjectMapper() with ScalaObjectMapper mapper.registerModule(DefaultScalaModule) val obj = mapper.readValue[Map[String, Object]](jsonString)

En la página de inicio de Play Framework afirman que "JSON es un ciudadano de primera clase". Todavía tengo que ver la prueba de eso.

En mi proyecto estoy tratando con algunas estructuras JSON bastante complejas. Este es solo un ejemplo muy simple:

{ "key1": { "subkey1": { "k1": "value1" "k2": [ "val1", "val2" "val3" ] } } "key2": [ { "j1": "v1", "j2": "v2" }, { "j1": "x1", "j2": "x2" } ] }

Ahora entiendo que Play está usando a Jackson para analizar JSON. Utilizo Jackson en mis proyectos Java y haría algo simple como esto:

ObjectMapper mapper = new ObjectMapper(); Map<String, Object> obj = mapper.readValue(jsonString, Map.class);

Esto podría analizar muy bien mi JSON en el objeto Map, que es lo que quiero: mapear pares de cadenas y objetos y me permitiría convertir fácilmente la matriz a ArrayList .

El mismo ejemplo en Scala / Play se vería así:

val obj: JsValue = Json.parse(jsonString)

Esto, en cambio, me da un type JsObject propietario que no es realmente lo que busco.

Mi pregunta es: ¿puedo analizar la cadena JSON en Scala / Play to Map lugar de JsObject tan fácilmente como lo haría en Java?

Pregunta lateral: ¿hay alguna razón por la que se use JsObject lugar de Map en Scala / Play?

Mi pila: Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit / Ubuntu 13.10 64bit

ACTUALIZACIÓN: Puedo ver que la respuesta de Travis es upvote, por lo que creo que tiene sentido para todos, pero todavía no veo cómo se puede aplicar para resolver mi problema. Digamos que tenemos este ejemplo (jsonString):

[ { "key1": "v1", "key2": "v2" }, { "key1": "x1", "key2": "x2" } ]

Bueno, de acuerdo con todas las instrucciones, ahora debería poner todo el contenido que de otra manera no entiendo el propósito de:

case class MyJson(key1: String, key2: String) implicit val MyJsonReads = Json.reads[MyJson] val result = Json.parse(jsonString).as[List[MyJson]]

Se ve bien para ir, ¿eh? Pero espere un minuto, llega otro elemento a la matriz que arruina totalmente este enfoque:

[ { "key1": "v1", "key2": "v2" }, { "key1": "x1", "key2": "x2" }, { "key1": "y1", "key2": { "subkey1": "subval1", "subkey2": "subval2" } } ]

El tercer elemento ya no coincide con mi clase de caso definida: estoy en la casilla uno de nuevo. Soy capaz de usar estructuras JSON mucho más complicadas en Java todos los días. ¿Scala sugiere que debería simplificar mis JSON para que se ajusten a su política de "seguridad de tipo"? Corríjame si me equivoco, pero ¿creo que ese lenguaje debería servir los datos, y no al revés?

ACTUALIZACIÓN2: La solución es usar el módulo Jackson para scala (ejemplo en mi respuesta).


Para mayor referencia y con el espíritu de simplicidad, siempre puede optar por:

Json.parse(jsonString).as[Map[String, JsValue]]

Sin embargo, esto generará una excepción para las cadenas JSON que no corresponden al formato (pero supongo que eso también se aplica al enfoque de Jackson). El JsValue ahora se puede procesar como:

jsValueWhichBetterBeAList.as[List[JsValue]]

Espero que la diferencia entre el manejo de Object s y JsValue s no sea un problema para usted (solo porque se quejó de que JsValue s es propietario). Obviamente, esto es un poco como la programación dinámica en un lenguaje mecanografiado, que generalmente no es el camino a seguir (la respuesta de Travis suele ser el camino a seguir), pero a veces es bueno tenerlo, supongo.


Recomendaría leer sobre ADT de coincidencia de patrones y recursivos en general para comprender mejor por qué Play Json trata a JSON como un "ciudadano de primera clase".

Dicho esto, muchas API de Java (como las bibliotecas Java de Google) esperan que JSON se deserialice como Map[String, Object] . Si bien puede simplemente crear su propia función que genere recursivamente este objeto con la coincidencia de patrones, la solución más sencilla probablemente sería utilizar el siguiente patrón existente:

import com.google.gson.Gson import java.util.{Map => JMap, LinkedHashMap} val gson = new Gson() def decode(encoded: String): JMap[String, Object] = gson.fromJson(encoded, (new LinkedHashMap[String, Object]()).getClass)

El LinkedHashMap se usa si desea mantener el orden de las claves en el momento de la deserialización (se puede usar un HashMap si el orden no es importante). Ejemplo completo here .


Scala, en general, desalienta el uso de downcasting, y Play Json es idiomático a este respecto. La reducción de proyecto es un problema porque hace imposible que el compilador lo ayude a rastrear la posibilidad de una entrada no válida u otros errores. Una vez que tenga un valor de tipo Map[String, Any] , estará solo: el compilador no puede ayudarlo a realizar un seguimiento de los valores de Any valor.

Tienes un par de alternativas. La primera es usar los operadores de ruta para navegar a un punto particular en el árbol donde conoce el tipo:

scala> val json = Json.parse(jsonString) json: play.api.libs.json.JsValue = {"key1": ... scala> val k1Value = (json / "key1" / "subkey1" / "k1").validate[String] k1Value: play.api.libs.json.JsResult[String] = JsSuccess(value1,)

Esto es similar a algo como lo siguiente:

val json: Map[String, Any] = ??? val k1Value = json("key1") .asInstanceOf[Map[String, Any]]("subkey1") .asInstanceOf[Map[String, String]]("k1")

Pero el enfoque anterior tiene la ventaja de fallar en formas más fáciles de razonar. En lugar de una excepción ClassCastException potencialmente difícil de interpretar, obtendríamos un buen valor JsError .

Tenga en cuenta que podemos validar en un punto más alto en el árbol si sabemos qué tipo de estructura esperamos:

scala> println((json / "key2").validate[List[Map[String, String]]]) JsSuccess(List(Map(j1 -> v1, j2 -> v2), Map(j1 -> x1, j2 -> x2)),)

Ambos de estos ejemplos de Play se basan en el concepto de clases de tipo, y en particular en instancias de la clase de tipo de Read proporcionada por Play. También puede proporcionar sus propias instancias de clase de tipo para los tipos que usted mismo ha definido. Esto te permitiría hacer algo como lo siguiente:

val myObj = json.validate[MyObj].getOrElse(someDefaultValue) val something = myObj.key1.subkey1.k2(2)

O lo que sea. La documentación de Play (enlazada arriba) proporciona una buena introducción a cómo hacerlo, y siempre puede hacer preguntas de seguimiento aquí si tiene problemas.

Para abordar la actualización en su pregunta, es posible cambiar su modelo para adaptarse a las diferentes posibilidades para key2 , y luego definir su propia instancia de key2 :

case class MyJson(key1: String, key2: Either[String, Map[String, String]]) implicit val MyJsonReads: Reads[MyJson] = { val key2Reads: Reads[Either[String, Map[String, String]]] = (__ / "key2").read[String].map(Left(_)) or (__ / "key2").read[Map[String, String]].map(Right(_)) ((__ / "key1").read[String] and key2Reads)(MyJson(_, _)) }

Que funciona así:

scala> Json.parse(jsonString).as[List[MyJson]].foreach(println) MyJson(v1,Left(v2)) MyJson(x1,Left(x2)) MyJson(y1,Right(Map(subkey1 -> subval1, subkey2 -> subval2)))

Sí, esto es un poco más detallado, pero es la verbosidad inicial la que paga por una vez (y eso le brinda algunas buenas garantías), en lugar de un montón de lanzamientos que pueden dar como resultado errores confusos de tiempo de ejecución.

No es para todos, y puede que no sea de su gusto, eso está perfectamente bien. Puede usar los operadores de ruta para manejar casos como este, o incluso el viejo Jackson. Sin embargo, te animo a que le des una oportunidad al enfoque de clase de tipos: hay una curva de aprendizaje abrupta, pero muchas personas (incluso yo) lo prefieren.


Simplemente puede extraer el valor de un Json y scala le da el mapa correspondiente. Ejemplo:

var myJson = Json.obj( "customerId" -> "xyz", "addressId" -> "xyz", "firstName" -> "xyz", "lastName" -> "xyz", "address" -> "xyz" )

Supongamos que tienes el Json del tipo anterior. Para convertirlo en mapa simplemente haz:

var mapFromJson = myJson.value

Esto le da un mapa de tipo: scala.collection.immutable.HashMap $ HashTrieMap