Reputation: 909
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.
Upvotes: 7
Views: 6077
Reputation: 909
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"
Upvotes: 4
Reputation: 2346
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.
Upvotes: 2
Reputation: 13859
Check out the Lift JSON API.
The key is that everything that gets parsed comes back as a subclass of JValue
. An object like
{
"a": [1, 2],
"b": "hello"
}
Would be parsed as
JObject(List(
JField("a", JArray(List(JInt(1), JInt(2)))),
JField("b", JString("hello"))
))
The Lift API provides some helpful methods like \
that let you access things like a Map
. There's also an extractOpt[A]
method that will try its best to turn the parsed JSON into whatever A
you want. Play around with them to get a feel for it.
Upvotes: 1