0

I am trying to parse a json using play framework into a case class. My intention here is to check whether a particular path exists in the json. If exists then only read element at that path.

Here is my code

package com.learning.avinash.query

import play.api.libs.json.{JsDefined, JsPath, Json, Reads, Writes, __}
import play.api.libs.functional.syntax._

object ParseJson extends App {

  case class Msisdn(primaryId: Option[String], relatedAccountId: Option[String])

  object Msisdn {
    implicit val readData: Reads[Msisdn] = (
      (JsPath \ "primary-id").readNullable[String] ~
      (JsPath \ "meta" \ "related-account-id").readNullable[String]
      ) (Msisdn.apply _)
  }



val testJson = """{
  "resources": [
    {
    "valid-for-start-datetime": "2019-08-23T10:47:17.485Z",
    "primary-id": "393823468684",
      "meta": {
        "related-account-id": "10001771",
            "roles": [
              "customer"
            ]
        }
    },
  {
    "valid-for-start-datetime": "2019-08-23T10:47:17.485Z",
    "primary-id": "393823467689"
    }
  ]
}"""
    println((Json.parse(testJson) \ "resources").as[List[Msisdn]])
}

In the resources array in the second object you can observe this code is completely missing

"meta": {
            "related-account-id": "10001771",
                "roles": [
                  "customer"
                ]
            }

Now when i try to parse the json, it fails and i get the following exception

Exception in thread "main" play.api.libs.json.JsResultException: JsResultException(errors:List(((1)/meta/related-accoount-id,List(JsonValidationError(List(error.path.missing),WrappedArray())))))

Is there a predefined function/method in play so that i can check whether this

(JsPath \ "meta")

particular path exists and then only read the element in that path. Or should i write a custom function to check whether the path exists.

I could see a JsDefined() which expects a Jsvalue.

https://www.playframework.com/documentation/2.7.x/api/scala/play/api/libs/json/index.html

final case classJsDefined(value: JsValue)
Wrapper for JsValue to represent an existing Json value.

Any thoughts, ideas, help or suggestions please.

1
  • there is grammar error in accoount (related-accoount-id). It is not reason of exception, of course Commented Dec 10, 2019 at 21:29

2 Answers 2

2

May be it will be helpful...

1-st way is use Json.format:

import play.api.libs.json._

object ParseJson extends App {

  case class Msisdn(`primary-id`: Option[String], meta: Option[Meta])
  case class Meta(`related-account-id`: Option[String])
  implicit val metaFormat = Json.format[Meta]
  implicit val msisdnFormat = Json.format[Msisdn]

Output: List(Msisdn(Some(393823468684),Some(Meta(Some(10001771)))), Msisdn(Some(393823467689),None))

Then you can use

case class Msisdn(private val `primary-id`: Option[String],
                  private val meta: Option[Meta]) {
  def primaryId: Option[String] = `primary-id`
  def accountId: Option[String] = meta.flatMap(_.`related-account-id`)
}

to change public API.

2-nd way is use recursive path. But I think you can use it carefully.

  case class Msisdn(primaryId: Option[String], relatedAccountId: Option[String])
  object Msisdn {
    implicit val readData: Reads[Msisdn] = (
      (JsPath \ "primary-id").readNullable[String] ~
        (JsPath \\ "related-account-id").readNullable[String]
      ) (Msisdn.apply _)
  }

3-rd is use new Format. In this case you can work with json object, and check that field is exists.

There is another way, but I forgot how to do it. It's way used fmap, probably.

Sorry for my English.

Sign up to request clarification or add additional context in comments.

6 Comments

There is no need for the case class fields not to respect the usually naming just because it differ from the JSON one.
@Mikhail Ionkin: The 2 way using recursive-path is not working. Even if the related-account-id value is present in the json, it is returning List(Msisdn(Some(393823468684),None), Msisdn(Some(393823467689),None))
@AvinashReddy it's because grammar error in accoount in question. I copy it. Now i fix answer
@cchantep which problems it's produce, if i will hide public API? I think that benefits of it covers disadvantages
Don't think so, JSON is JSON, Scala is Scala
|
0

There is no flatten annotation available, but playing by the rules make it easier.

import play.api.libs.json._

case class MsisdnMeta(relatedAccountId: Option[String])

object MsisdnMeta {
  private implicit val jsonConfiguration: JsonConfiguration =
    JsonConfiguration(JsonNaming { _ => "related-account-id" })

  implicit val format: OFormat[MsisdnMeta] = Json.format
}

case class Msisdn(
  primaryId: Option[String],
  meta: Option[MsisdnMeta]) {

  @inline def relatedAccountId: Option[String] =
    meta.flatMap(_.relatedAccountId)
}

object Msisdn {
  private implicit val jsonConfiguration: JsonConfiguration =
    JsonConfiguration(JsonNaming {
      case "primaryId" => "primary-id"
      case _ => "meta"
    })

  implicit val format: OFormat[Msisdn] = Json.format
}

Then reading/parsing JSON:

val json = Json.parse("""{
  "valid-for-start-datetime": "2019-08-23T10:47:17.485Z",
  "primary-id": "393823468684",
  "meta": {
    "related-account-id": "10001771",
    "roles": [
      "customer"
    ]
  }
}""")

val res = json.validate[Msisdn]
// play.api.libs.json.JsResult[Msisdn] = JsSuccess(Msisdn(Some(393823468684),Some(MsisdnMeta(Some(10001771)))),)

res.foreach { suc =>
  println(s"relatedAccountId = ${suc.relatedAccountId}")
}
// relatedAccountId = Some(10001771)

... and writing to JSON:

Json.toJson(Msisdn(Some("id"), Some(MsisdnMeta(Some("bar")))))
// play.api.libs.json.JsValue = {"primary-id":"id","meta":{"related-account-id":"bar"}}

The creation of Msisdn with the nested meta can be easier with some factory added to the companion object.

object Msisdn {
  // Added factory
  @inline def apply(
    primaryId: Option[String],
    relatedAccountId: String): Msisdn =
    Msisdn(primaryId, Some(MsisdnMeta(Some(relatedAccountId))))

  private implicit val jsonConfiguration: JsonConfiguration =
    JsonConfiguration(JsonNaming {
      case "primaryId" => "primary-id"
      case _ => "meta"
    })

  implicit val format: OFormat[Msisdn] = Json.format
}

Json.toJson(Msisdn(Some("id"), "bar"))
// play.api.libs.json.JsValue = {"primary-id":"id","meta":{"related-account-id":"bar"}}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.