vendredi 17 mars 2017

Programmatically generate JSONPath or similiar from a case class

I'd like to generate all the possible JSONPath from a given case class itself and not an instance of the case class (preferably).

Simple case class with simple nesting:

case class Inner(innerA: String, innerB: String, innerC: String)
case class TestCase(a: String, b: String, inner: Inner)

For TestCase, I'd like to produce (prefer declaration order but not a must):

root.a
root.b
root.inner
root.inner.innerA
root.inner.innerB
root.inner.innerC

Or something that'll easily transform into valid JSONPath. The purpose for this is so we can define several case class in Scala and provide the user with a list of valid JSONPath that they can use to extract information.

Right now, there's no problem doing:

  1. case class to JSON
  2. JSON to case class
  3. Retrieve a value via JSONPath (as a string) from JSON by using gatling (see here) with something like JsonPath.query(jsonPathString, jsonObj)

Using circe it looks like I can do one step further and do root.inner.innerA (see here) and get/set the value. However, I couldn't figure out how to produce the list of JSONPath using either circe or gatling (which feels like doing the reverse of what is being offered by those libraries).

An alternative I can think of is to recursively go through classOf[TestCase].getDeclaredFields. Further searching produces:

  1. Scala. Get field names list from case class
  2. Scala 2.10 reflection, how do I extract the field values from a case class

However, it looks like I still need to recursively go through each field and see if there's anything nested. Is this the best approach?

Samples:

  • Instance of case class : TestCase(a = "alpha", "beta", inner = Inner(innerA = "innerAlpha", innerB = "innerBeta", innerC = "foo"))
  • JSON produced of above: {"a":"alpha","b":"beta","inner":{"innerA":"innerAlpha","innerB":"innerBeta","innerC":"foo"}}
  • JSONPath evaluator : http://jsonpath.com/ (e.g. $.inner.innerA)

Current solution(?):

def recursive(members: MemberScope, parent: String): Unit = {
  members.foreach { member =>
    member match {
      case m: MethodSymbol if m.isCaseAccessor =>
        println(s"$parent.${m.name}")
        recursive(m.returnType.members, s"$parent.${m.name}")
      case _ =>
    }
  }
}

recursive(typeOf[TestCase].members, "$")

Which produces:

$.inner
$.inner.innerC
$.inner.innerB
$.inner.innerA
$.b
$.a

EDIT: Supposedly shapeless is able to do this but I haven't quite figured it out.





Aucun commentaire:

Enregistrer un commentaire