mhamrah
mhamrah

Reputation: 9358

Scala: Refactoring a case-statement to use for-comprehension

I'm trying to parse the following Json into a Scala object:

{
  "oneOf": [    
    { "$ref": "..." },
    { "$ref": "..." },
    { "$ref": "..." }
}

The field "oneOf" could also be "anyOf" or "allOf"; it will only be one of these values. I am constructing a case class, ComplexType, using Play's JSON library. The logic is simple; it looks for a given field and reads it if present, otherwise, checks a different field.

(json \ "allOf") match {
    case a:JsArray => ComplexType("object", "allOf", a.as[Seq[JsObject]].flatMap(_.values.map(_.as[String])))
    case _ => 
        (json \ "anyOf") match {
            case a:JsArray => ComplexType("object", "anyOf", a.as[Seq[JsObject]].flatMap(_.values.map(_.as[String])))
            case _ =>
                  (json \ "oneOf") match {
                        case a:JsArray => ComplexType("object", "oneOf", a.as[Seq[JsObject]].flatMap(_.values.map(_.as[String])))
                        case _ => ComplexType("object", "oneOf", "Unspecified" :: Nil)
                      }
                  }
              } 

I'm not happy with this syntax; even though it works I don't see why I need to have nested match statements if no match is found. I believe a for-comprehension will work well: I can check for (json \ "allOf"), (json \ "oneOf) etc in a guard clause and yield the available result, but not sure how to get the syntax correct.

Is there a more elegant way to build this case class?

Thanks,

Mike

Upvotes: 1

Views: 503

Answers (1)

senia
senia

Reputation: 38045

I don't think for-comprehension is useful here.

You could make your code more readable with custom extractors like this:

class Extractor(s: String) {
  def unapply(v: JsValue): Option[JsArray] = json \ s match {
    case a: JsArray => Some(a)
    case _ => None
  }
}

val AllOf = new Extractor("allOf")
val AnyOf = new Extractor("anyOf")
val OneOf = new Extractor("oneOf")

val (name, es) = json match {
    case AllOf(a) => "allOf" -> Some(a)
    case AnyOf(a) => "anyOf" -> Some(a)
    case OneOf(a) => "oneOf" -> Some(a)
    case _ => "oneOf" -> None
  }
val result =
  es.
    map{ a => ComplexType("object", name, a.as[Seq[JsObject]].flatMap(_.values.map(_.as[String]))) }.
    getOrElse("Unspecified" :: Nil)

Upvotes: 2

Related Questions