Search code examples
jsonscalaparsingjson4s

Problems converting JSON objects in Scala


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.


Solution

  • 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.