Search code examples
jsonscalaplayframeworkplay-json

Validate JSON in Play framework without JSPath


I have pre-defined class MyClass the following JSON structure:

{
  "key1": [
      // Objects of MyClass
  ],

  "key2": [
      // Objects of MyClass
  ],

  "key3": [
      // Objects of MyClass
  ]
}

key1, key2, key3 are optional (at least one must present but I do not need to validate it) According to the Play framework documentation I need to do the following:

implicit val myClassReads: Reads[] = (
  (JsPath \ "key1").read[List[MyClass]] and
  (JsPath \ "key2").read[List[MyClass]] and
  (JsPath \ "key3").read[List[MyClass]]
)

But this approach has some disadvantages :

  1. The data type of key1, key2, key3 is always MyClass). Repeating read[MyClass] seems redundant.

  2. What if the JSON schema changes in away that there are key1, key2, ... key100 ? The code becomes very messy.

How can I write a custom validator that apply read[MyClass] to all fields?


Solution

  • If you are using Play Json 2.8.x, you can just do:

    val result = json.validate[Map[String, List[MyClass]]]
    

    Is it possible to validate whether the JSON keys are in ALLOWED_KEYS: List[String] with your approach?

    You can also use another type for the key of the Map, as long as you define a KeyReads instance for that type. So you could use an Enum, with only a limited number of possible values. Or you can use refinement types (e.g. using the "refined" library) and so on.

    The easiest way to restrict the possible values of the keys is to check the resulting Map after conversion:

    val ALLOWED_KEYS = Set("key1", "key2", ...)
    
    val result = json.validate(
      Reads.verifying[Map[String, List[MyClass]]](_.keySet.subsetOf(ALLOWED_KEYS))
    )
    

    or you can also check the keys upfront if you want:

    val result = json.validate(
      Reads.verifying[JsObject](_.keys.subsetOf(ALLOWED_KEYS))
        .andThen(Reads.of[Map[String, List[MyClass]]])
    )