talex
talex

Reputation: 20566

JSON arrays and Scala Seq

I have scala class like class A(b:Seq[String])

My problem is when I deserialize it from text which have no b field my class contains null. It is possible to force deserealizer to fill with empty Seq?

I use com.fasterxml.jackson.databind.ObjectMapper with com.fasterxml.jackson.module.scala.DefaultScalaModule.

EDIT: I want solution that fix all such fields without explicitly mentioning full list of them. Ir changing all declarations.

Upvotes: 4

Views: 1147

Answers (3)

leoconco
leoconco

Reputation: 291

I had a similar problem deserializing String arrays with jackson, and found that jackson apparently (have not tried yet) fixed this in jackson-module 2.1.2. It might work for Seq. https://github.com/FasterXML/jackson-module-scala/issues/48

Upvotes: 0

mattinbits
mattinbits

Reputation: 10428

If you use Spray JSON, then a simple example which doesn't handle the absence of the b field would look like:

import spray.json._

case class A(b: Seq[String])

object Protocol extends DefaultJsonProtocol {
  implicit def aFormat = jsonFormat1(A)
}

import Protocol._

val str1 = """{ "b" : [] }""""
val str2 = """{ "b" : ["a", "b", "c"] }""""
val str3 = """{}"""
str1.parseJson.convertTo[A]//successful
str2.parseJson.convertTo[A]//successful
str3.parseJson.convertTo[A]//Deserialization error

In Spray JSON, this can be solved by writing a more detailed protocol format for class A:

import spray.json._

case class A(b: Seq[String])

object Protocol extends DefaultJsonProtocol {

  implicit object aFormat extends RootJsonFormat[A] {
    override def write(obj: A): JsValue = JsObject("b" -> obj.b.toJson)

    override def read(json: JsValue): A = json match {
      //Case where the object has exactly one member, 'b'
      case JsObject(map) if map.contains("b") && map.size == 1 => A(map("b").convertTo[Seq[String]])
      //Case where the object has no members
      case JsObject(map) if map.isEmpty => A(Seq())
      //Any other json value, which cannot be converted to an instance of A
      case _ => deserializationError("Malformed")
    }
  }
}

import Protocol._

val str1 = """{ "b" : [] }""""
val str2 = """{ "b" : ["a", "b", "c"] }""""
val str3 = """{}"""
str1.parseJson.convertTo[A]//successful
str2.parseJson.convertTo[A]//successful
str3.parseJson.convertTo[A]//successful

Upvotes: 1

Rich
Rich

Reputation: 15464

This is not currently supported by Jackson, unfortunately.

You can see the relevant GitHub ticket here: https://github.com/FasterXML/jackson-databind/issues/347

Your best bet is to map null into an empty Seq in the class constructor or in an accessor method:

class A(_b: Seq[String]) {
  val b = _b match {
    case null => Nil
    case bs => bs
  }
}

(See also https://stackoverflow.com/a/20655330/8261 for other options)

Upvotes: 5

Related Questions