I'm trying to make a simple example of a class serialization in Scala using json4s library, but even after extensively searching for it on the internet, unfortunately I couldn't find any satisfatory sample that would solve my problem.
Basically I have a simple class called Person
and I want to extract an instance of this class from a JSON string.
case class Person(
val name: String,
val age: Int,
val children: Option[List[Person]]
)
So when I do this:
val jsonStr = "{\"name\":\"Socrates\", \"age\": 70}"
println(Serialization.read[Person](jsonStr))
I get this output:
"Person(Socrates,70,None)" // works fine!
But when I have no age parameter in JSON string, I get this error:
Exception in thread "main" org.json4s.package$MappingException: No usable value for age
I know that Person
class has two required parameters in its constructor, but I would like to know if there's a way to make this conversion through a parser or something similar.
Also, I've tried to make this parser, but no success.
Thanks in advance for any help.
Assuming you do not want to make age optional by setting its type to Option
, then you could write a custom serializer by extending CustomSerializer[Person]
. The custom serializer takes a function from Formats
to a tuple of PartialFunction[JValue, Person]
(your Person
deserializer) and PartialFunction[Any, JValue]
(your serializer).
Assuming Person.DefaultAge
is the default value you want to set for age if the age is not given, then the custom serializer could look as follows:
object PersonSerializer extends CustomSerializer[Person](formats => ( {
case JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: Nil) => Person(name, age.toInt, None)
case JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: JField("children", JArray(children)) :: Nil) => Person(name, age.toInt, Some(children map (child => formats.customDeserializer(formats).apply(TypeInfo(classOf[Person], None), child).asInstanceOf[Person])))
case JObject(JField("name", JString(name)) :: Nil) => Person(name, Person.DefaultAge, None)
case JObject(JField("name", JString(name)) :: JField("children", JArray(children)) :: Nil) => Person(name, Person.DefaultAge, Some(children map (child => formats.customDeserializer(formats).apply(TypeInfo(classOf[Person], None), child).asInstanceOf[Person])))
}, {
case Person(name, age, None) => JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: Nil)
case Person(name, age, Some(children)) => JObject(JField("name", JString(name)) :: JField("age", JInt(age)) :: JField("children", formats.customSerializer(formats).apply(children)) :: Nil)
}))
This could probably be simplified as there is a lot of repetition. Also, there might be a better way to recursively call serialization/deserialization.