2
votes

I'm trying to traverse JSON and extract information about types of objects in fields:

import org.json4s._
import org.json4s.JsonAST.{JArray, JString, JObject, JInt, JBool, JDouble}
import org.json4s.JsonDSL._
import org.json4s.native.JsonMethods._

def guessTypes(example: JValue): JObject = example match {
case JObject(lst) => JObject(lst.map {
  case (f, JObject(nested)) => JField(f, ("type" -> "object") ~ ("properties" -> guessTypes(nested)))
  case (f, JString(s)) => JField(f, "type" -> "string")
  case (f, JInt(num)) => JField(f, "type" -> "integer")
  case (f, JDouble(double)) => JField(f, "type" -> "double")
  case (f, JBool(b)) => JField(f, "type" -> "bool")
  case (f, JArray(jarray: List[JInt])) => JField(f, ("type" -> "array") ~ ("items" -> "integer"))
  case (f, JArray(jarray: List[JString])) => JField(f, ("type" -> "array") ~ ("items" -> "string"))
  case (f, JArray(jarray: List[JObject])) => JField(f, ("type" -> "array") ~ ("items" -> "object")) ~ ("properties" -> jarray.map{ x => guessTypes(x)}))
})}

In case of:

def example = """
    |{
    |  "partners_data": [
    |    {
    |      "info": {
    |        "label": "partner45"
    |      },
    |      "partner_id": "partner45",
    |      "data": {
    |        "field": 24
    |      }
    |    }
    |  ],
    |  "name": "*****(",
    |  "location": [
    |    1,
    |    2
    |  ],
    |  "is_mapped": false
    |}
  """.stripMargin

the result is obtained:

{"data":{"type":"array","items":"object"},"name":{"type":"string"},"location":{"type":"array","items":"object"},"is_mapped":{"type":"bool"}}

This's not appropriate result, because for key "items" in "location" value "integer" is expected.

It looks like Scala can't distinguish anything else except JValue in JArrays. If I replace

case (f, JArray(jarray: List[JInt]))

by last string, for key "items" in "location" value "object" will be obtained, but for the others fields value will be wrong.

How can I get round this peculiarity of scala pattern matching and json4s framework?

1
What version of Json4s are you using? When using "org.json4s" %% "json4s-native" % "3.2.2" I get the correct types.millhouse
I'm using "org.json4s" %% "json4s-native" % "3.2.11". In our case there is no chance to use earlier version of json4s.ilyakhov

1 Answers

1
votes

The last three patterns are essentially the same because of type erasure on the JVM.

Since a JSON array can contain multiple (Scala/Java/...) types it is difficult to match the type of the elements of the list.

You could check only the first item of the array :

case (f, JArray(JString(_) :: tail)) => 
  JField(f, ("type" -> "array") ~ ("items" -> "string"))
case (f, JArray(jarray @ JObject(_) :: tail)) => 
  JField(f, ("type" -> "array") ~ ("items" -> "object") ~ ("properties" -> jarray.map(guessTypes)))

Or check every item in the array:

case (f, JArray(list : List[JValue])) if list forall { case JInt(_) => true; case _ => false } => 
  JField(f, ("type" -> "array") ~ ("items" -> "integer"))