Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I deserialize from JSON with Scala using *non-case* classes?

Tags:

json

scala

I'm writing a Scala app that needs to serialize to and deserialize from JSON. Some of the JSON objects have more than 22 fields so I can't use case classes (and I can't change the format either). All the Scala JSON libraries that I have been able to find only work (easily) with case classes, not with normal classes.

Given that, what is the easiest way to deserialize a large JSON object (with more than 22 fields) into a Scala non-case class? It doesn't have to be completely automatic, but ideally I'm looking for something less painful than deserializing to a Map[String, Any] and manually doing everything.

like image 280
Ryan Avatar asked Dec 18 '12 03:12

Ryan


People also ask

How do I deserialize an object in JSON?

A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

Is Scala case class serializable?

You can also serialize / deserialize Scala case classes with other formats, like the MessagePack binary format.

What is serialization and deserialization in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).


2 Answers

Update: Fortunately, it is now possible to do what I wanted using Json4s and Jackson using a field serializer as follows:

implicit val formats = DefaultFormats + FieldSerializer[MyNonCaseClass]()

val myNonCaseClassObject = Serialization.read[MyNonCaseClass](jsonString)

As requested below here's a more complete example:

import org.json4s.jackson.Serialization
import org.json4s._
import scala.util.Try

object JSONUtil {

implicit val formats = DefaultFormats + FieldSerializer[MyNonCaseClass]() + FieldSerializer[MyOtherNonCaseClass](ignore("someUnwantedFieldName") orElse ignore("anotherFieldToIgnore")) + ...

def toJSON(objectToWrite: AnyRef): String = Serialization.write(objectToWrite)

def fromJSONOption[T](jsonString: String)(implicit mf: Manifest[T]): Option[T] = Try(Serialization.read(jsonString)).toOption

}

Then usage is:

val jsonString = JSONUtil.toJSON(myObject)
val myNewObject: Option[MyClass] = JSONUtil.fromJSONOption[MyClass](aJsonString)

You need a FieldSerializer for every non-case class you wish to serialize. Also, when defining your classes, everything that might be missing from the JSON needs to be defined as an Option.

SBT:

"org.json4s" %% "json4s-jackson" % "3.2.6"
like image 86
Ryan Avatar answered Sep 21 '22 13:09

Ryan


It's possible to do it without case classes using The Play JSON library with generics

As I was drinking some coffee and doing nothing. I took the liberty to code an example for you. The complete solution is as follows:

First of all, this is your class:

import play.api.libs.json._
import play.api.libs.json.Json._


    class TestJSON(
      val field1: String,
      val field2: String,
      val field3: String,
      val field4: String,
      val field5: String,
      val field6: String,
      val field7: String,
      val field8: String,
      val field9: String,
      val field10: String,
      val field11: String,
      val field12: String,
      val field13: String,
      val field14: String,
      val field15: String,
      val field16: String,
      val field17: String,
      val field18: String,
      val field19: String,
      val field20: String,
      val field21: String,
      val field22: String,
      val field23: String) {

    }

    object TestJSON {
      //
      // JSON BINDING/UNBINDING
      //

      implicit def modalityReads: Reads[TestJSON] = new Reads[TestJSON] {
        def reads(json: JsValue): TestJSON =
          new TestJSON(
            field1 = (json \ "field1").as[String],
            field2 = (json \ "field2").as[String],
            field3 = (json \ "field3").as[String],
            field4 = (json \ "field4").as[String],
            field5 = (json \ "field5").as[String],
            field6 = (json \ "field6").as[String],
            field7 = (json \ "field7").as[String],
            field8 = (json \ "field8").as[String],
            field9 = (json \ "field9").as[String],
            field10 = (json \ "field10").as[String],
            field11 = (json \ "field11").as[String],
            field12 = (json \ "field12").as[String],
            field13 = (json \ "field13").as[String],
            field14 = (json \ "field14").as[String],
            field15 = (json \ "field15").as[String],
            field16 = (json \ "field16").as[String],
            field17 = (json \ "field17").as[String],
            field18 = (json \ "field18").as[String],
            field19 = (json \ "field19").as[String],
            field20 = (json \ "field20").as[String],
            field21 = (json \ "field21").as[String],
            field22 = (json \ "field22").as[String],
            field23 = (json \ "field22").as[String])
      }

      implicit def modalityWrites: Writes[TestJSON] = new Writes[TestJSON] {
        def writes(ts: TestJSON) = JsObject(Seq(
          "field1" -> JsString(ts.field1),
          "field2" -> JsString(ts.field2),
          "field3" -> JsString(ts.field3),
          "field4" -> JsString(ts.field4),
          "field5" -> JsString(ts.field5),
          "field6" -> JsString(ts.field6),
          "field7" -> JsString(ts.field7),
          "field8" -> JsString(ts.field8),
          "field9" -> JsString(ts.field9),
          "field10" -> JsString(ts.field10),
          "field11" -> JsString(ts.field11),
          "field12" -> JsString(ts.field12),
          "field13" -> JsString(ts.field13),
          "field14" -> JsString(ts.field14),
          "field15" -> JsString(ts.field15),
          "field16" -> JsString(ts.field16),
          "field17" -> JsString(ts.field17),
          "field18" -> JsString(ts.field18),
          "field19" -> JsString(ts.field19),
          "field20" -> JsString(ts.field20),
          "field21" -> JsString(ts.field21),
          "field22" -> JsString(ts.field22),
          "field23" -> JsString(ts.field23)))
      }
    }

Your controller should look like this:

import play.api._
import play.api.mvc._
import play.api.libs.json.Json._
import play.api.Play.current
import models.TestJSON

object Application extends Controller  {

  def getJson = Action {
    implicit request =>
      Ok(
        toJson(
          Seq(
            toJson(
              new TestJSON(
                "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
                "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23")),
              toJson(new TestJSON(
                "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
                "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23")))))
  }

}

Your route file (just route the action):

GET     /getJson                             controllers.Application.getJson

And now, the moment of truth...

curl localhost:9000/getJson
[{"field1":"1","field2":"2","field3":"3","field4":"4","field5":"5","field6":"6",
"field7":"7","field8":"8","field9":"9","field10":"10","field11":"11","field12":"
12","field13":"13","field14":"14","field15":"15","field16":"16","field17":"17","
field18":"18","field19":"19","field20":"20","field21":"21","field22":"22","field
23":"23"},{"field1":"1","field2":"2","field3":"3","field4":"4","field5":"5","fie
ld6":"6","field7":"7","field8":"8","field9":"9","field10":"10","field11":"11","f
ield12":"12","field13":"13","field14":"14","field15":"15","field16":"16","field1
7":"17","field18":"18","field19":"19","field20":"20","field21":"21","field22":"2
2","field23":"23"}]

It should work the other way around too. I'm currently working in a project that uses that to assemble and disassemble huge trees, so it should work for you. Let me know.

Cheers!

PS: Don't worry, it took me about 10 minutes to generate the code. I just mapped a List.range(1,24) and "foreached" it to print the code.

like image 26
wleao Avatar answered Sep 21 '22 13:09

wleao