How to create a JSON of a List of List of Any in Scala

951 Views Asked by At

The JSON output that I am looking for is

{[[1, 1.5, "String1"], [-2, 2.3, "String2"]]}

So I want to have an Array of Arrays and the inner array is storing different types.

How should I store my variables so I can create such JSON in Scala?

I thought of List of Tuples. However, all the available JSON libraries try to convert a Tuple to a map instead of an Array. I am using json4s library.

2

There are 2 best solutions below

0
On

Here is a custom serializer for those inner arrays using json4s:

import org.json4s._

class MyTupleSerializer extends CustomSerializer[(Int, Double, String)](format => ({
  case obj: JArray =>
    implicit val formats: Formats = format

    (obj(0).extract[Int], obj(1).extract[Double], obj(2).extract[String])
}, {
  case (i: Int, d: Double, s: String) =>
    JArray(List(JInt(i), JDouble(d), JString(s)))
}))

The custom serialiser converts JArray into a tuple and back again. This will be used wherever the Scala object being read or written has a value of the appropriate tuple type.

To test this against the sample input I have modified it to make it valid JSON by adding a field name:

{"data": [[1, 1.5, "String1"], [-2, 2.3, "String2"]]}

I have defined a container class to match this:

case class MyTupleData(data: Vector[(Int, Double, String)])

The name of the class is not relevant but the field name data must match the JSON field name. This uses Vector rather than Array because Array is really a Java type rather than a Scala type. You can use List if preferred.

import org.json4s.jackson.Serialization.{read, write}

case class MyTupleData(data: Vector[(Int, Double, String)])

object JsonTest extends App {
  val data = """{"data": [[1, 1.5, "String1"], [-2, 2.3, "String2"]]}"""
  implicit val formats: Formats = DefaultFormats + new MyTupleSerializer

  val td: MyTupleData = read[MyTupleData](data)

  println(td) // MyTupleData(Vector((1,1.5,String1), (-2,2.3,String2)))

  println(write(td)) // {"data":[[1,1.5,"String1"],[-2,2.3,"String2"]]}
}

If you prefer to use a custom class for the data rather than a tuple, the code looks like this:

case class MyClass(i: Int, d: Double, s: String)

class MyClassSerializer extends CustomSerializer[MyClass](format => ({
  case obj: JArray =>
    implicit val formats: Formats = format

    MyClass(obj(0).extract[Int], obj(1).extract[Double], obj(2).extract[String])
}, {
  case MyClass(i, d, s) =>
    JArray(List(JInt(i), JDouble(d), JString(s)))
}))
0
On

Use a List of List rather than List of Tuples.

an easy way to convert list of tuples to list of list is:

val listOfList: List[List[Any]] = listOfTuples.map(_.productIterator.toList)

I would use jackson, which is a java library and can deal with arbitrary datatypes inside collections of type Any/AnyRef, rather than trying to come up with a custom serializer in one of scala json libraries.

To convert scala List to java List use

import collection.JavaConverters._

So, in summary the end list would be:

val javaListOfList: java.util.List[java.util.List[Any]] = listOfTuples.map(_.productIterator.toList.asJava).asJava

Using this solution, you could have arbitrary length tuples in your list and it would work.

import com.fasterxml.jackson.databind.ObjectMapper

import collection.JavaConverters._

object TuplesCollectionToJson extends App {

  val tuplesList = List(
    (10, false, 43.6, "Text1"),
    (84, true, 92.1, "Text2", 'X')
  )

  val javaList = tuplesList.map(_.productIterator.toList.asJava).asJava

  val mapper = new ObjectMapper()
  val json = mapper.writeValueAsString(javaList)
  println(json)

}

Would produce:

[[10,false,43.6,"Text1"],[84,true,92.1,"Text2","X"]]

PS: Use this solution only when you absolutely have to work with variable types. If your tuple datatype is fixed, its better to create a json4s specific serializer/deserializer.