0

The result of Json4s decoding frequently scramble the sequence of element in a JObject if decoding into a HashMap, so I tried to decode into ListMap instead. However, there seems to be no way of doing this, when I run the following simple program:

val v: ListMap[String, Int] = ListMap("a" -> 1, "b" -> 2)
val json = JsonMethods.compact(Extraction.decompose(v))
val v2 = Extraction.extract[ListMap[String, Int]](JsonMethods.parse(json))
assert(v == v2)

The following error message was thrown:

scala.collection.immutable.Map$Map2 cannot be cast to scala.collection.immutable.ListMap
java.lang.ClassCastException: scala.collection.immutable.Map$Map2 cannot be cast to scala.collection.immutable.ListMap

Is there an easy way to fix this? Or should I switch to more recent Json libraries (Argonaut/Circe) instead?

tribbloid
  • 4,026
  • 14
  • 64
  • 103

2 Answers2

0

No, you can't do this. At least not this way. According to the JSON spec

An object is an unordered set of name/value pairs.

And all the standard libraries treat it that way. It means that the order is already scrambled when you/library do the initial parsing into intermediate data structure. Moreover, you can't even guarantee that the JSON will be {"a":1, "b":2} instead of {"b":2, "a":1}

The only way to preserve the order is to store in inside the JSON in a way that enforces the order and the only such thing is an ordered list of values aka array. So you can do something like this:

  val v: ListMap[String, Int] = ListMap("c" -> 1, "AaAa" -> 2, "BBBB" -> 3, "AaBB" -> 4, "BBAa" -> 5)

  val jsonBad = JsonMethods.compact(Extraction.decompose(v))
  val bad = Extraction.extract[Map[String, Int]](JsonMethods.parse(jsonBad))
  val jsonGood = JsonMethods.compact(Extraction.decompose(v.toList))
  val good = ListMap(Extraction.extract[List[(String, Int)]](JsonMethods.parse(jsonGood)): _*)

  println(s"'$jsonBad' => $bad")
  println(s"'$jsonGood' => $good")

Which prints

'{"c":1,"AaAa":2,"BBBB":3,"AaBB":4,"BBAa":5}' => Map(AaAa -> 2, BBBB -> 3, AaBB -> 4, BBAa -> 5, c -> 1)
'[{"c":1},{"AaAa":2},{"BBBB":3},{"AaBB":4},{"BBAa":5}]' => ListMap(c -> 1, AaAa -> 2, BBBB -> 3, AaBB -> 4, BBAa -> 5)

SergGr
  • 23,570
  • 2
  • 30
  • 51
0

Here is a library, which supports all Scala collections, so you can parse and serialize to/from ListMap easy, also it serializes case class fields in stable order of declaration:

libraryDependencies ++= Seq(
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "0.29.2" % Compile, 
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "0.29.2" % Provided // required only in compile-time
)

import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

val codec = JsonCodecMaker.make[ListMap[String, Int]](CodecMakerConfig())
val v: ListMap[String, Int] = ListMap("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4, "e" -> 5)
val json = writeToArray(codec, v)
val v2 = readFromArray(codec, json)
require(v == v2)
Community
  • 1
  • 1
Andriy Plokhotnyuk
  • 7,883
  • 2
  • 44
  • 68