2017-10-05 4 views
1

En utilisant jackson-module-Scala, j'essaie de sérialiser et de désérialiser un objet avec une carte interne en utilisant un long comme clé, mais le Jackson sérialise la clé comme Chaîne et ne la désérialise pas en tant qu'Ignorer le type défini dans la Classe. Est-ce un BUG? Est-ce que je fais quelque chose de mal?jackson-module-scala serialize/deserialize Longues clés dans Map as Strings

import com.fasterxml.jackson.databind.ObjectMapper 
import com.fasterxml.jackson.module.scala.DefaultScalaModule 
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper 

case class InnerMap(map: Map[Long, Long]) 

object CrazyJackson { 

    def main(args: Array[String]): Unit = { 
    val mapper = new ObjectMapper() with ScalaObjectMapper 
    mapper.registerModule(DefaultScalaModule) 

    val innerMap = InnerMap(Map(1L->1L)) 
    val serialized = mapper.writeValueAsString(innerMap) 
    val newObj = mapper.readValue(serialized, classOf[InnerMap]) 
    println(serialized) // Why the key is serialized as a String? 
    println(innerMap) 
    println(newObj) 
    assert(newObj == innerMap) 
    } 

} 

assert échoue et la sortie du println (sérialisés) déclaration est:

{"map":{"1":1}} 

Il est étrange que l'impression newobj et innerMap est le même:

InnerMap(Map(1 -> 1)) 
InnerMap(Map(1 -> 1)) 

Comme @ Varren dit, le problème est vraiment dans l'affirmation. Mais:

import com.fasterxml.jackson.databind.ObjectMapper 
import com.fasterxml.jackson.module.scala.DefaultScalaModule 
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper 
import org.scalatest.FunSuite 

class CrazyJacksonTest extends FunSuite { 
    test("test json comparision") { 
    val mapper = new ObjectMapper() with ScalaObjectMapper 
    mapper.registerModule(DefaultScalaModule) 

    val innerMap = InnerMap(Map(1L->1L)) 
    val serialized = mapper.writeValueAsString(innerMap) 
    val newObj = mapper.readValue(serialized, classOf[InnerMap]) 
    assert(newObj.map == innerMap.map) 
    } 
} 

Le résultat assert:

Map("1" -> 1) did not equal Map(1 -> 1) 
ScalaTestFailureLocation: CrazyJacksonTest$$anonfun$1 at (CrazyJacksonTest.scala:17) 
Expected :Map(1 -> 1) 
Actual :Map("1" -> 1) 

Je suis perdu! La carte doit être une Carte [Long, Long]!

Je dois utiliser cette version en raison de dépendances Spark:

  • Scala 2.11.11
  • jackson-module-scala 2.6.5 et tester aussi avec la version 2.9.1 avec le même résultat.

Autres infos:

Répondre

0

JSON permet des noms clés pour être des chaînes uniquement. ECMA-404 The JSON Data Interchange Standard

une structure d'objet est représenté par une paire de jetons d'accolade entourant zéro ou plusieurs paires nom/valeur. Un nom est une chaîne.

Vous avez raison et le problème d'assertion vient de Jackson. enter image description here Comme vous pouvez le voir classOf[InnerMap] fait la carte à Map<Object, Object> à l'intérieur InnerMap mais vous devez soumettre typeinfo de cette carte à jackson pour le désérialiser correctement. Il est expliqué dans this documentation et selon elle, vous pouvez simplement utiliser

case class InnerMap(@JsonDeserialize(keyAs = classOf[java.lang.Long]) 
        map: Map[Long, Long]) 
+0

Donc, le problème est avec l'assert. Je pensais que les classes de cas implémentent equals et hashCode. En fait, pourquoi "assert (InnerMap (Map (1L-> 1L)) == InnerMap (Map (1L-> 1L)))" ok? – angelcervera

+0

S'il vous plaît, pouvez-vous vérifier mon nouvel exemple? Il me manque quelque chose. Comment est-il possible que l'assertion gère l'objet interne comme Map [String, Long] si la classe de cas est définie comme Map [Long, Long] – angelcervera

+0

Le lien vers "Comment comparer les objets pour l'égalité dans Scala?" est un cas différent, car il s'agit de comparer des classes et non des classes de cas. – angelcervera

0

Le module Jackson Scala ne déduit pas le type de la clé dans la carte. Comme @Varren a répondu, la solution est d'annoter le modèle avec annotations Jackson, mais de cette façon:

  • Le modèle est lié à un analyseur spécifique (annotations Jackson dans la définition du modèle).
  • Le code est moins clair.

donc j'ai décidé de passer de Jackson à Circe et supprimer les annotations pour garder le code propre. Ceci est un test qui prouve qu'il est l'analyse et unparsing correctement:

test("test json circe comparision") { 

    import io.circe._ 
    import io.circe.generic.auto._ 
    import io.circe.parser._ 
    import io.circe.syntax._ 

    val innerMap = InnerMap(Map(1L -> 1L)) 

    val jsonStr = innerMap.asJson.noSpaces 
    decode[InnerMap](jsonStr) match { 
     case Right(innerMap2) => assert(innerMap2 == innerMap) 
     case Left(error) => fail(error) 
    } 

    } 

Il ne signifie pas que c'est la meilleure solution pour tout le monde. Circé a un plugin pour l'utiliser en combinaison avec Jackson parser, mais je ne l'ai pas testé.