3

My function is receiving a JsValue, now this json have lists, and this lists element could also be lists, for example:

{
  "firstName": "Elon",
  "lastName": "Musk",
  "companies": [
    {
      "name": "Tesla",
      "city": "San Francisco",
      "offices": ["sf", "ny"],
      "management": {
        "loscation": "New York",
        "boardMembers": [
          {
            "name": "John",
            "age": 45
          },
          {
            "name": "Mike",
            "age": 55
          },
          {
            "name": "Rebecca",
            "age": 35
          }
        ]
      }
    },
    {
      "name": "SpaceX",
      "city": "San Francisco",
      "offices": ["la", "ta"],
      "management": {
        "loscation": "San Mateo",
        "boardMembers": [
          {
            "name": "Lisa",
            "age": 45
          },
          {
            "name": "Dan",
            "age": 55
          },
          {
            "name": "Henry",
            "age": 35
          }
        ]
      }
    }
  ]
}

So a company have management object, which have boardMembers list.

My function is receiving the path to this element, for example:

companies[*].management.boardMembers[*].name

I want it to return a list with all the elements of this object, so the result will be:

["John", "Mike", "Rebecca", "Lisa", "Dan", "Henry"]

It's a bit complicated, but I thought maybe play.api.libs.json._ would have some feature that could help.

thought about splitting it pathStr.split("(\\[\\*]\\.|\\[\\*])").toList and then iterate to get all elements some how and return JsLookupResult but not sure how.

Just to clarify:

My method will receive 2 parameters, the JsValue and the path as string def myFunc(json: JsValue, path: String)

every time I call myFunc it can receive different path, I'm not sure what it will be only after myFunc was called.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35
JohnBigs
  • 2,691
  • 3
  • 31
  • 61

2 Answers2

2

You can do:

val jsPath = JsPath \ "companies" \\ "management" \ "boardMembers" \\ "name"
val result = jsPath(Json.parse(input))
println(result)

Which will print the expected output. See Scastie example.

Please note the difference between \ and \\:

  • \ looks for direct children
  • \\ looks for recursive children

To implement myFunc you can try something like this:

def findAllValuesAtPath(jsValue: JsValue, path: String): List[JsValue] = {
  val jsPath = JsPath(path
    .split("\\[\\*]\\.")
    .flatMap(s => s.split("\\.")
      .map(RecursiveSearch)
    ).toList)
  println(jsPath.path)
  jsPath(jsValue)
}

Here is another scastie.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35
  • thanks Tomer! that works well for most cases, but if I want just to reference to a simple array like: ```{ "person": { "kids": [ "jack", "rose" ] } }``` path: "person.kids" you will get ```List(["jack","rose"])```, do you have a suggestion for that? I would expect to get the array like ```List("jack","rose")``` – JohnBigs Feb 20 '22 at 08:20
  • 1
    sure! accepted. but its not really a different question mate, i meant it to be included in this one as a scenario. you right prob my bad for not describing it properly. I just want this function to handle either path for complex obj list with specific member (which this works already with this suggestion) or jut path to a simple list and return this list...@Tomer Shetah – JohnBigs Feb 20 '22 at 09:37
  • any suggestion for edit that will catch this scenario my friend? – JohnBigs Feb 20 '22 at 11:51
  • I thought of posting another question but it will be completely out of context, it should def be part of this as its the complete behaviour requested..@Tomer Shetah – JohnBigs Feb 21 '22 at 09:22
  • uploaded a new question if you don't mind https://stackoverflow.com/questions/71220791/use-jslookup-for-multiple-level-array-using-play-json-for-lists-with-objects-and – JohnBigs Feb 22 '22 at 12:09
0

As you can see in the documentation, you can use the Reads typeclass to define the way to decode types from JSON.

import play.api.libs.json._

val input = """{
  "firstName": "Elon",
  "lastName": "Musk",
  "companies": [
    {
      "name": "Tesla",
      "city": "San Francisco",
      "offices": ["sf","ny"],
      "management": {
        "loscation": "New York",
        "boardMembers": [
          {
            "name": "John",
            "age": 45
          },
          {
            "name": "Mike",
            "age": 55
          },
          {
            "name": "Rebecca",
            "age": 35
          }
        ]
      }
    },
    {
      "name": "SpaceX",
      "city": "San Francisco",
      "offices": ["la","ta"],
      "management": {
        "loscation": "San Mateo",
        "boardMembers": [
          {
            "name": "Lisa",
            "age": 45
          },
          {
            "name": "Dan",
            "age": 55
          },
          {
            "name": "Henry",
            "age": 35
          }
        ]
      }
    }
  ]
}"""

val json = Json.parse(input)

// ---

case class BoardMember(name: String, age: Int)

implicit val br: Reads[BoardMember] = Json.reads

case class Company(boardMembers: Seq[BoardMember])

implicit val cr: Reads[Company] = Reads {
  case obj @ JsObject(_) =>
    (obj \ "management" \ "boardMembers").validate[Seq[BoardMember]].map {
      Company(_)
    }

  case _ =>
    JsError("error.obj.expected")
}

val reads = Reads[Seq[String]] {
  case obj @ JsObject(_) =>
    (obj \ "companies").validate[Seq[Company]].map {
      _.flatMap(_.boardMembers.map(_.name))
    }

  case _ =>
    JsError("error.obj.expected")
}

// ---

json.validate(reads)
//  play.api.libs.json.JsResult[Seq[String]] = JsSuccess(Vector(John, Mike, Rebecca, Lisa, Dan, Henry),)
cchantep
  • 9,118
  • 3
  • 30
  • 41
  • no I don't know the path, its being passed to my function, the path I mentioned is an example...the question was, if for example im getting this path, how can I return 1 list of all the lists in that path – JohnBigs Nov 09 '21 at 14:06
  • my function will receive json as JsValue and path that look like the example, and I need to return list of all the elements of that path, like the example response I mentioned ```["John", "Mike", "Rebecca", "Lisa", "Dan", "Henry"]``` – JohnBigs Nov 09 '21 at 14:08
  • You have to know the path – cchantep Nov 09 '21 at 14:08
  • sorry I do know the path but its changing every time the method is called – JohnBigs Nov 09 '21 at 14:13
  • that means I need to build the reader differently every time, and im not working with case classes only jsValues cause this json can change – JohnBigs Nov 09 '21 at 14:16
  • If you cannot define the schema of the input, I would say it's symptom of bad design – cchantep Nov 09 '21 at 15:02