0

I want to filter my json in the attributes field.

The json is like this :

"subject": "cars",
"price": [25],
"location":{city: "Oslo", postalcode :441},
"attributes" :
[{"key":"tc", "value":2, "key_label":typecars", "value_label":"WPD"}, 
{"key":"lk", "value":1, "key_label":"lookupcars", "valnotEqualReadsue_label":"fed"}
{"key":, "value":6, "key_label":"year", "value_label":"fzef"}
{"key":"nc", "value":3, "key_label":ncars", "value_label":"POD"}
{"key":"tc", "value":4, "key_label":plcars", "value_label":"LOD"}
{"key":"pm", "value":5, "key_label":pomcars", "value_label":"PLD"}]

I want to keep only key_label and value_label where key_label ="year" and "pomcars". How can I do that ?

I tried

case class Attribut(key_label: Option[String]                    
                   , value_label: Option[String]
)

case class Ads(subject: Option[String]
           , price: Option[Int]
           , location: Option[Location]
           , attribut: Option[Seq[Attribut]]

)

 implicit val adsReads: Reads[Ads] = (
 (JsPath \ "subject").readNullable[String] and
 (JsPath \ "price" \ 0).readNullable[Int] and
 (JsPath \ "location").readNullable[Location] and
 (JsPath \ "attributes").readNullable[Seq[Attribut]].filter {
 case Some(Attribut(Some(key_label), _)) => (key_label == "year") || key_label == "pomcars")
 case _                                  => false
                }  
 ) (Ads.apply _)

I have the following error : constructor cannot be instantiated to expected type; found : Attribut required: Seq[Attribut]

Thank you for your help.

fleur
  • 29
  • 5
  • I don't understand what you're trying to do - are you just keeping objects where `only key_label ="year" + associated value_label : "fzef" and key_label="pomcars" + associated value_label "PLD"`? Do you want the entire object or just the `key_label` and `value_label` fields? – James Whiteley Oct 05 '20 at 11:10
  • Are you keeping the objects where that condition is true or removing them? – James Whiteley Oct 05 '20 at 11:14
  • It is better to keep deserialization as simple as it can be. So I suggest you trivially deserialize and filter it after in business logic. Because filtering/validations seem like business logic. The transport layer should be trivial. – Artem Sokolov Oct 05 '20 at 11:24
  • @JamesWhiteley : I am keeping only the key_label and the value label where the condition key_label ="year" and key_label="pomcars" is true. I don't want to keep the fields key and value. – fleur Oct 05 '20 at 11:43
  • @ArtemSokolov : When you suggest deserialize, do you mean doing something like this link : https://stackoverflow.com/questions/18027233/jackson-scala-json-deserialization-to-case-classes/21874583 – fleur Oct 05 '20 at 11:47
  • 1
    @fleur deserialize just means to take a String and turn it into an object. https://stackoverflow.com/questions/3316762/what-is-deserialize-and-serialize-in-json - eg turning JSON into a case class (what your Reads is trying to do) – James Whiteley Oct 05 '20 at 12:20

1 Answers1

1

The easiest way to do this would be to just read in the JSON and deal with it once it's a Seq of case classes.

For example, read it in like this:

case class Attribute(key_label: Option[String], value_label: Option[String])

object Attribute {
  implicit val reads: Reads[Attribute] = Json.reads[Attribute]
}

json.as[Seq[Attribute]] // or `.asOpt` if you can't guarantee that it'll work

Then you can do something like filtering out values you don't want or folding over the Seq, for example:

json.as[Seq[Attribute]].filter {
  case Attribute(Some(key_label), _) => (key_label == "year") || (key_label == "pomcars")
  case _                             => false
}

or:

json.as[Seq[Attribute]].foldLeft(Seq[Attribute]()) {
  case (acc, curr@Attribute(Some(key_label), _))
    if (key_label == "year") || (key_label == "pomcars") => acc :+ curr

  case (acc, _)                                          => acc
}

These both result in res0: Seq[Attribute] = List(Attribute(Some(year),Some(fzef)), Attribute(Some(pomcars),Some(PLD))).

See live on Scastie

James Whiteley
  • 3,363
  • 1
  • 19
  • 46
  • I have changed the question because i don't put the whole json and maybe it's not clear. I tried to reuse your solution but it does not work because I have to use something like (JsPath \ "attributes").readNullable[Seq[Attribut]].filter { case Some(Attribut(Some(key_label), _)) => (key_label == "year") || key_label == "pomcars") case _ => false } Thank you for your help – fleur Oct 05 '20 at 13:36
  • You're still doing the filtering inside the Reads, you're making life much more difficult for yourself. If you really want to do that, you'll need to do something like `(JsPath \ "attributes").readNullable[Seq[Attribute]].map(_.map(_.filter {...}))` - first `.map` to get into the reads, second `.map` to get into the Option, `.filter` to filter through the Seq. – James Whiteley Oct 05 '20 at 13:57
  • Like [this](https://scastie.scala-lang.org/RQ129uK0ThKwu97BYvtIQg) @fleur – James Whiteley Oct 05 '20 at 13:58
  • 1
    Use `.validate` rather than unsafe `.as` – cchantep Oct 08 '20 at 10:40