Search code examples
javascalaimmutabilitycase-classjson4s

Mapping POJOs to case classes with immutable lists during deserialisation


I am coming from Java background and trying to understand how to model Domain classes/POJOs in Scala.

I am trying to Deserialize JSON response from a RestAPI and my Java POJOs are as follows:

@Data
public class ColumnResponse {
    private String id;
    private String name;
    private String type;
    ...
}

k

@Data
public class DataSetGetResponse {
    private String id;
    private List<ColumnResponse> columns;
    ...
}

Now I have created following Case Classes

case class DataSetGetResponse (id: String,
                               columns: List[ColumnResponse]
                              .... )


case class ColumnResponse (id: String,name: String ...)

I am trying to use https://sttp.readthedocs.io/en/latest/json.html#json4s library for HTTP communication and json4s for deserialization.

Questions:

1) In the DataSetGetResponse case class, field "columns" is a List.By default this is an immutable list. How the Deserialization library add new DataColumnGetResponse objects to this immutable list? Do I have to declare this as mutable ?

2) There is a field called 'type' field in the ColumnResponse POJO. In Scala 'type' is a reserved keyword.How to handle this case?


Solution

  • This answer addresses the following aspect of the question:

    How the Deserialization library add new DataColumnGetResponse objects to this immutable list?

    Let us consider a simplified version of the problem:

    JsonMethods.parse("""[1,2,3]""").extract[List[Int]]
    

    How does json4s deserialise [1,2,3] into immutable List[Int]? First it parses the raw string into an intermediary AST (abstract syntax tree) data structure where it represents the list like so

    case class JArray(arr: List[JValue]) extends JValue
    

    We see here that arr is an immutable list. The key line that builds it up after parse executes is in JsonParser

        def newValue(v: JValue): Unit = {
          ...
            case a: JArray => vals.replace(JArray(v :: a.arr))
          ...
        }
    

    Note how the operator :: in v :: a.arr adds an element at the beginning of this list and returns a new list with v added in. This means since there are three elements in [1,2,3] the following three lists are created by json4s in the process of deserialisation

    JArray(List(JInt(1))
    JArray(List(JInt(2), JInt(1)))
    JArray(List(JInt(3), JInt(2), JInt(1)))
    

    Again note these are three separate lists.

    Next, after internal AST is created, actual deserialisation to List[Int] takes place by calling extract[List[Int]]. The key component that does this for lists is CollectionBuilder

      private class CollectionBuilder(json: JValue, tpe: ScalaType)(implicit formats: Formats) {
        ...
          val array: Array[_] = json match {
            case JArray(arr)      => arr.map(extractDetectingNonTerminal(_, typeArg)).toArray
        ...
        }
    

    Note how we simply map over AST arr built up during parsing step and convert each element to the model of type typeArg, which in our simple case is Int but in your case would be DataColumnGetResponse.