I'm Scala na json4s to consume json. To deserialize I'm calling org.json4s.native.JsonMethods.parse and ExtractableJsonAstNode.extract method.
This is a part of json file:
"": {
"atribute1": "v1",
"instanceId": "i",
},
It contains attribute without name.
What should be field name in case class to successfully deserialize attributes?
I think you can't parse such json into case class. Unless you do a custom deserializer for it and then you can decide it yourself.
import org.json4s.{JValue, CustomSerializer, DefaultFormats}
import org.json4s.native.JsonMethods
import org.json4s.JsonDSL._
import org.json4s._
case class Outer(value: Inner, other: String)
case class Inner(atribute1: String, instanceId: String)
object Formats extends DefaultFormats {
val outerSerializer = new CustomSerializer[Outer](implicit format ⇒ (
{ case j: JValue ⇒ Outer(
(j \ "").extract[Inner],
(j \ "other").extract[String]
)},
{ case a: Outer ⇒
("" → Extraction.decompose(a.value)) ~
("other" → a.other)
})
)
override val customSerializers = List(outerSerializer)
}
implicit val formats = Formats
val json = """
{
"": {
"atribute1": "v1",
"instanceId": "i",
},
"other": "1"
}
"""
JsonMethods.parse(json).extract[Outer]
Related
The following data can be seen with different value types. How can I get the desired output?
package ceshi
import scala.util.parsing.json.JSON
object ceshi1212 {
def main(args: Array[String]): Unit = {
class CC[T] {
def unapply(a: Any): Option[T] = Some(a.asInstanceOf[T])
}
object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
val jsonString =
"""
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
}
,{
"name": "Latin",
"is_active": "0009"
}
,
"error"
]
}
""".stripMargin
// 不规则json error和并列的数组类型不同 怎么解析自动跳过?
val result = for {
Some(M(map)) <- List(JSON.parseFull(jsonString))
L(languages) = map("languages")
M(language) <- languages
S(name) = language("name")
S(active) = language("is_active")
S(completeness) = language.getOrElse("completeness","--")
} yield {
(name, active, completeness)
}
println(result)
//i want result is: List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
}
}
i want get result is List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
note: 1 The string is not always at the end of the array, and the position is indeterminate
2 The three keys I need may not be complete
As said in the comments there are other libraries to be recommended for working with json have a look at this post to get an overview: What JSON library to use in Scala?
Answer to your question with specific framework (play-json)
Personally I can recommend to use the play json framework.
To obtain the result you have described with play json, your code might look like this:
import play.api.libs.json._
val json: JsValue = Json.parse(jsonString)
val list = (json \ "languages").as[Seq[JsValue]]
val names = list.map(x => ((x\"name").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val isActives = list.map(x => ((x\"is_active").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val completeness = list.map(x => ((x\"completeness").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
// need to know in advance what is your max length of your tuple (tmax)
// since 3rd value "completeness" can be missing, so we just take "" instead
val tmax = 3
val res = for(idx <-0 to tmax-1) yield (names(idx),isActives(idx),completeness(idx))
res.toList
// List[(String, String, String)] = List((English,true,asdf), (Latin,asdf,232), (Latin,0009,""))
There's also a very good documentation for the play json framework, just check it out yourself: https://www.playframework.com/documentation/2.8.x/ScalaJson
If you can switch parser library to circe, you can deal with this types of bad data.
Given you have data model
import io.circe.generic.semiauto._
import io.circe.parser.decode
import io.circe.{Decoder, Json}
case class Languages(languages: Seq[Language])
case class Language(name: String, is_active: String, completeness: Option[String])
You can define a fault-tolerant seq decoder that would skip bad data rather than crash whole parse
def tolerantSeqDecoder[A: Decoder]: Decoder[Seq[A]] = Decoder.decodeSeq(Decoder[A]
.either(Decoder[Json])).map(_.flatMap(_.left.toOption))
and the rest...
val jsonString = """
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
},
{
"name": "Latin",
"is_active": "0009"
},
"error"
]
}
"""
val languageDecoder = deriveDecoder[Language]
implicit val tolerantDecoder = tolerantSeqDecoder[Language](languageDecoder)
implicit val languagesDecoder = deriveDecoder[Languages]
val parsed = decode[Languages](jsonString)
println(parsed)
out:
Right(Languages(List(Language(English,true,Some(asdf)), Language(Latin,asdf,Some(232)), Language(Latin,0009,None))))
This approach was suggested by one of circe developers: How do I ignore decoding failures in a JSON array?
Since I'm writing a function to request data from another API in my Scala code, the response Json has the format like this:
"data": {
"attributeName": "some String",
"attributeValue": false,
"attributeSource": "Manual",
"attributeValueLabel": null
},
"data": {
"attributeName": "some String",
"attributeValue": "daily",
"attributeSource": "Manual",
"attributeValueLabel": "Almost Daily"
}
Note that sometimes the type of attributeValue is String value, some other time it's a Boolean value.
So I'm trying to write my own Reads and Writes to read the type dynamically.
case class Data(attributeName: Option[String], attributeValue: Option[String], attributeSource: Option[String], attributeValueLabel: Option[String])
object Data{
implicit val readsData: Reads[Data] = {
new Reads[Data] {
def reads(json: JsValue) = {
val attrValue = (json \ "attributeValue").as[] // How to cast to Boolean some time, but some other time is a String here
......
}
}
}
So as you can see in my comment, I'm stuck at the part to cast the (json \ "attributeValue") to String/Boolean, base on the return type of the API. How can I do this?
You can try to parse it as String first and then as Boolean:
val strO = (json \ "attributeValue").asOpt[String]
val value: Option[String] = strO match {
case str#Some(_) => str
case None => (json \ "attributeValue").asOpt[Boolean].map(_.toString)
}
You can use the .orElse function when you are trying to read an attribute in different ways:
import play.api.libs.json.{JsPath, Json, Reads}
import play.api.libs.functional.syntax._
val json1 =
"""
|{
| "attributeName": "some String",
| "attributeValue": false
|}
""".stripMargin
val json2 =
"""
|{
| "attributeName": "some String",
| "attributeValue": "daily"
|}
""".stripMargin
// I modified you case class to make the example short
case class Data(attributeName: String, attributeValue: String)
object Data {
// No need to define a reads function, just assign the value
implicit val readsData: Reads[Data] = (
(JsPath \ "attributeName").read[String] and
// Try to read String, then fallback to Boolean (which maps into String)
(JsPath \ "attributeValue").read[String].orElse((JsPath \ "attributeValue").read[Boolean].map(_.toString))
)(Data.apply _)
}
println(Json.parse(json1).as[Data])
println(Json.parse(json2).as[Data])
Output:
Data(some String,false)
Data(some String,daily)
I need to extract a field value from the message:
{
"data": {
"code": "404",
...
}
}
JSONPath expression is stored in a variable:
val path = "/data/code"
I'm using json4s for manipulating JSON.
From the doc, one can achieve this using DSL:
val json = parse(message)
val code = json \ "data" \ "code"
It works, but obviously the JSONPath expression should be hardcoded.
Is there any way to evaluate expression stored as string?
Something like:
val code = json.evaluateJSONPath(path)
assuming the json path is provided in a string format delimited by comma the below would work.
import org.json4s._
import org.json4s.native.JsonMethods._
val json = parse(""" { "data": { "code": "404", "foo": "bar" } } """)
val path = "data,code" // json path in string
path.split(',').foldLeft(json)({ case (acc, node) => acc \ node })
Edit:
Adding a implicit class to simplify the access
implicit class JsonHelper(json: JValue) {
def customExtract[T](path: String)(implicit formats: Formats, mf: Manifest[T]) = {
path.split(',').foldLeft(json)({ case (acc: JValue, node: String) => acc \ node }).extract[T]
}
}
json.customExtract[String]("data,code")
res23: String = "404"
How about use the high order function to map the extract command, like:
val extractCodeFunc = (j: JValue) => j \ "data" \ "code"
and extract json:
val res = extractCodeFunc.apply(json)
So I have a structure in json looking like this:
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
I'm having the hardest time converting this to a list of case classes. I'm sure I'm missing something completely obvious...
I have tried this:
case class Person(name: String)
implicit val personReads: Reads[Email] = (__ \\ "name").read[String](Person)
// endpoint
def person = Action { request =>
val person = request.body.asJson.get.as[Seq[Person]]
}
which doesn't compile since read doesn't return a FunctionBuilder which means I can't apply the path to Person
Adding a new parameter does compile (changing the json and case class accordingly):
case class Person(name: String, age: String)
implicit val personReads: Reads[Email] = (
(__ \\ "name").read[String]) and
(__ \\ "age").read[Int](Person)
but throws an exception Execution exception[[JsResultException: JsResultException(errors:List((,List(ValidationError(List(error.expected.jsarray),WrappedArray())))))]] supposedly because it expects a list.
So I tried adding this:
implicit val personsReads: Reads[Seq[Person]] = (__ \ "lst").read[Seq[Person]]
which then throws a NullPointer.
In the end I just want a Seq[Person].
Can anyone point me in the right direction, I'm completely lost to what I'm expected to do here...
You can do the following instead of giving reads and writes explicitly.
import play.api.json.Json
case class Person(name: String)
object Person {
implicit val personFormat = Json.format[Person]
}
case class Persons(lst: List[Person])
object Persons {
implicit val personsFormat = Json.format[Persons]
}
Now take the json string lets say jsonStr
Json.parse(jsonStr).validate[Persons] match {
case JsSuccess(persons, _) => println(persons)
case JsError(_) => println("parsing failed")
}
Scala REPL
scala> import play.api.libs.json._
import play.api.libs.json._
scala> val str = """{
| "lst": [
| {"name": "foo"},
| {"name": "bar"}
| ]
| }""".stripMargin
str: String =
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Person(name: String)
object Person {
implicit val personFormat = Json.format[Person]
}
case class Persons(lst: List[Person])
object Persons {
implicit val personsFormat = Json.format[Persons]
}
// Exiting paste mode, now interpreting.
defined class Person
defined object Person
defined class Persons
defined object Persons
scala> val jsonStr = str
jsonStr: String =
{
"lst": [
{"name": "foo"},
{"name": "bar"}
]
}
scala> :paste
// Entering paste mode (ctrl-D to finish)
Json.parse(jsonStr).validate[Persons] match {
case JsSuccess(persons, _) => println(persons)
case JsError(_) => println("parsing failed")
}
// Exiting paste mode, now interpreting.
Persons(List(Person(foo), Person(bar)))
Now when you change your Person case class and add age field.
case class Person(name: String, age: Int)
object Person {
implicit val personFormat = Json.format[Person]
}
Ensure that the json you are trying to parse contains both name and age. If you have only name then you will get parsing error.
I am trying to parse a Json object that consists only of an top level array without a key.
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Name(first: String, last: String)
case class Names(names: Seq[Name])
implicit val NameF = Json.format[Name]
val s = """[{"first": "A", "last": "B"},{"first": "C", "last": "D"},{"first": "E", "last": "F"}]"""
implicit val NF: Reads[Names] = (
JsPath.read[Seq[Name]]
)(Names.apply _)
<console>:34: error: overloaded method value read with alternatives:
(t: Seq[Name])play.api.libs.json.Reads[Seq[Name]] <and>
(implicit r: play.api.libs.json.Reads[Seq[Name]])play.api.libs.json.Reads[Seq[Name]]
cannot be applied to (Seq[Name] => Names)
JsPath.read[Seq[Name]]
One possiblity is by creating an implicit Reads function:
def readNames: Reads[Names] = new Reads[Names] {
def reads(json: JsValue) = {
json.validate[Seq[Name]].map(succ => Names(succ))
}
}
implicit val NamesFormat = readNames
You don't need to specify Reads for Seq[Name] if you already have one defined for Name.
case class Name(first: String, last: String)
implicit val NameF = Json.format[Name]
val s = """[{"first": "A", "last": "B"},{"first": "C", "last": "D"},{"first": "E", "last": "F"}]"""
scala> Json.parse(s).validate[Seq[Name]]
res2: play.api.libs.json.JsResult[Seq[Name]] = JsSuccess(List(Name(A,B), Name(C,D), Name(E,F)),)