简体   繁体   中英

Play: How to validate a JSON where an attribute might be of diverse types?

I need a JSON structure that contains a name/value pair... and the value might be either a String or a List[String] :

{
  "name":"id",
  "value":"55e9cf3e0100003700dd2134"
}

{
  "name":"id",
  "value":["55e9cf3e0100003700dd2134","55e9e3430100004000d73b59"]
}

Here below is the class I use to deal with such a structure:

class NameValue protected(protected var json: JsValue) {

  def name = json as (__ \ 'name).read[String]
  def name_= (v: String) = setValue((__ \ 'name), Json.toJson(v))
  def value = json \ "value" match {
    case v: JsString => v.value
    case v => v.as[List[String]].head
  }
  def value_= (v: String) = setValue((__ \ 'value), Json.toJson(v))
  def multiValue = json \ "value" match {
    case v: JsString => List(v.value)
    case v => v.as[List[String]]
  }
  def multiValue_= (v: List[String]) = setValue((__ \ 'value), Json.toJson(v))
} 

The value method returns

  1. The value as-is in case of String
  2. The first element in case of List[String]

The multiValue method returns

  1. The value as-is in case of List[String]
  2. A List[String] containing one element in case of String

Here below is the companion object that creates NameValue instances and validates its JSON:

object NameValue {

  import play.api.libs.functional.syntax._
  import play.api.libs.json.Reads._

  def apply(json: JsValue): JsResult[NameValue] = {
    validateNameValue.reads(json).fold(
      valid = { validated => JsSuccess(new NameValue(validated)) },
      invalid = { errors => JsError(errors) }
    ) 
  } 

  def apply(name: String, value: String): NameValue = new NameValue(
    nameValueWrites.writes(name, Some(value), None) 
  )  

  def apply(name: String, multiValue: List[String]): NameValue = new NameValue(
    nameValueWrites.writes(name, None, Some(multiValue)) 
  )   

  def unapply(nameValue: NameValue) = {
    if (nameValue eq null) None
    else {
      val multiValue = rameValue.multiValue
      Some((
        nameValue.name,
        if (multiValue.length > 1) multiValue else multiValue.head
      ))
    }
  }

  implicit val nameValueFormat = new Format[NameValue] {
    def reads(json: JsValue) = NameValue(json)
    def writes(nameValue: NameValue) = nameValue.json
  }

  private val nameValueWrites = (
    (__ \ 'name).write[String] ~
    (__ \ 'value).writeNullable[String] ~
    (__ \ 'value).writeNullable[List[String]]
  ).tupled

  private val multiValue: Reads[JsArray] = {
    (__ \ 'value).json.pick[JsArray] andThen verifying[JsArray](_.value.nonEmpty)
  }

  val validateNameValue = (
    ((__ \ 'name).json.pickBranch) ~
    ((__ \ 'value).json.pickBranch)
  ).reduce

  // how do I integrate this in `validateNameValue`?
  val validateNameValue2 = (
    ((__ \ 'name).json.pickBranch) ~
    ((__ \ 'value).json.copyFrom(multiValue))
  ).reduce
}

Everything works fine... but I'd like to have just one validator regardless of whether the value attribute is a String or a List[String] . How do I create a single validator that replaces validateNameValue and validateNameValue2 ?

Differentiate value into String and a list of String is not a good practice. A List can also contain a single element.

In addition, you can use Json.format to get rid of boilerplate code of defining reader and writer for a case class.

 import play.api.libs.json.Json

 object NameValue {
    implicit val format = Json.format[NameValue]
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM