I am trying to pass this json string to my other method but SOMETIMES I get this error,
play.api.libs.json.JsResultException:
JsResultException(errors:List((,List(ValidationError(error.expected.jsstring,WrappedArray())))))
I find it strange that this occurs randomly, sometimes I don't get the exception and sometimes I do. Any ideas?
Here is what my json looks like
val string = {
"first_name" : {
"type" : "String",
"value" : "John"
},
"id" : {
"type" : "String",
"value" : "123456789"
},
"last_name" : {
"type" : "String",
"value" : "Smith"
}
}
I read it like
(string \ "first_name").as[String]
(string \ "first_name") gives JsValue not JsString so as[String] does not work.
But if you need first name value you can do
val firstName = ((json \ "first_name") \ "value").as[String]
This is another option to consider for type safety while reading and writing Json with play Json API. Below I am showing using your json as example reading part and writing is analogous.
Represent data as case classes.
case Person(id:Map[String,String], firstName:Map[String,String], lastName:[String,String]
Write implicit read/write for your case classes.
implicit val PersonReads: Reads[Person]
Employ plays combinator read API's. Import the implicit where u read the json and you get back the Person object. Below is the illustration.
val personObj = Json.fromJson[Person](json)
Please have a look here https://www.playframework.com/documentation/2.6.x/ScalaJsonAutomated
First of all you can't traverse a String object, so your input must be converted to JsValue to be able to traverse it:
val json = Json.parse(string)
then the first_name attribut is of JsValue type and not a String so if you try to extract the first_name value like you do you will get always a JsResultException.
I can only explain the random appearance of JsResultException by a radom change in your first_name field json structure. make sur your sting input to traverse has always a fixed first_name filed structure.
Related
I am trying to familiarize myself with the PlayJSON library. I have a JSON formatted file like this:
{
"Person": [
{
"name": "Jonathon",
"age": 24,
"job": "Accountant"
}
]
}
However, I'm having difficulty with parsing it properly due to the file having different types (name is a String but age is an Int). I could technically make it so the age is a String and call .toInt on it later but for my purposes, it is by default an integer.
I know how to parse some of it:
import play.api.libs.json.{JsValue, Json}
val parsed: JsValue = Json.parse(jsonFile) //assuming jsonFile is that entire JSON String as shown above
val person: List[Map[String, String]] = (parsed \ "Person").as[List[Map[String, String]]]
Creating that person value throws an error. I know Scala is a strongly-typed language but I'm sure there is something I am missing here. I feel like this is an obvious fix too but I'm not quite sure.
The error produced is:
JsResultException(errors:List(((0)/age,List(JsonValidationError(List(error.expected.jsstring),WrappedArray())))
The error you are having, as explained in the error you are getting, is in casting to the map of string to string. The data you provided does not align with it, because the age is a string. If you want to keep in with this approach, you need to parse it into a type that will handle both strings and ints. For example:
(parsed \ "Person").validate[List[Map[String, Any]]]
Having said that, as #Luis wrote in a comment, you can just use case class to parse it. Lets declare 2 case classes:
case class JsonParsingExample(Person: Seq[Person])
case class Person(name: String, age: Int, job: String)
Now we will create a formatter for each of them on their corresponding companion object:
object Person {
implicit val format: OFormat[Person] = Json.format[Person]
}
object JsonParsingExample {
implicit val format: OFormat[JsonParsingExample] = Json.format[JsonParsingExample]
}
Now we can just do:
Json.parse(jsonFile).validate[JsonParsingExample]
Code run at Scastie.
Scala 2.12 here. I'm trying to use Lift-JSON to deserialize some JSON into a Scala object and am having trouble navigating the Lift API. Please note: I'm not married to Lift-JSON, any other working solution will be accepted so long as I don't have to bring any heavy/core Play dependencies into my project.
Here's the JSON file I'm trying to read:
{
"fizz" : "buzz",
"foo" : [
"123",
"456",
"789"
],
"bar" : {
"whistle" : 1,
"feather" : true
}
}
Here's my Scala object hierarchy:
case class Bar(whistle : Integer, feather : Boolean)
case class MyConfig(fizz : String, foo : Array[String], bar : Bar)
And finally my best attempt at the codeup for this:
def loadConfig(configFilePath : String) : MyConfig = {
val configJson = Source.fromFile(configFilePath)
val parsedJson = parse(configJson.mkString)
MyConfig(???)
}
I need validation in place so that if the JSON is not valid an exception is thrown. Any ideas how I can extract fields out of parsedJson and use them to set values for my MyConfig instance? And how to perform the validation?
Have you tried parsedJson.extract[MyConfig]? That is straight out of the Extracting values documentation. If you haven't already, you will need to specify an implicit reference to the default formats:
implicit val formats = DefaultFormats
I'm trying to extract certain fields from Scala object before converting to Json. Is there an easy way to do this.
It would also work if i could make a new Json with certain fields from a Json.
You can simply extract out the value of a Json and scala gives you the corresponding map. Example:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
Suppose you have the Json of above type. To convert it into map simply do:
var mapFromJson = myJson.value
This gives you a map of type : scala.collection.immutable.HashMap$HashTrieMap
Hard to say without more details. Suppose that you have the following Scala case class...
case class SomeObject(customerId: Long, addressId: Long, firstName: String, lastName: String, address: String)
...and that you wanted to extract the 'firstName', 'lastName', and address fields and then convert the object to Json. Using play-json you could define an implicit conversion on the companion object for the SomeObject class...
object SomeObject {
implicit val someObjectWrites = new Writes[SomeObject] {
def writes(object: SomeObject) = Json.obj(
"firstName" -> object.firstName,
"lastName" -> object.lastName,
"address" -> object.address
)
}
}
Then you could just use the code as follows:
val obj = SomeObject(12345, 678910, "John", "Doe", "My Address")
val json = Json.toJson(obj)
Note that there are probably other JSON libraries, besides play-json, that support similar functionality.
Hi everyone recently I faced an issue in converting json into my own data model.
I have a json format message which may contain an empty string:
{
"name" : "John Doe",
"hobbies": ""
}
or a list of hobby types:
{
"name" : "John Doe",
"hobbies": [{"name":"basketball"}]
}
And the following is my case class data model in scala play framework:
case class Person(name: String, hobbies: List[Hobby])
case class Hobby(name: String)
Right now I'm using the default json formatter but of course it's not working well when we have empty string as value.
implicit val HobbyJson= Json.format[Hobby]
implicit val PersonJson = Json.format[Person]
it will throw exception if the hobbies has a empty string. I want to convert it into an empty list when it's the empty string. I search the document Play provides but couldn't find infomation. Can anyone give some suggestions?
Thanks in advance.
As you mentioned, the default Format macros won't work for you here because of the inconsistent treatment of hobbies. So you need to implement your own Reads[Person] - here's how I'd do it:
object PersonJson {
implicit val hobbyConverter = Json.format[Hobby]
val personReads = new Reads[Person] {
override def reads(json: JsValue): JsResult[Person] = {
for {
personName <- (json \ "name").validate[String]
hobbies <- (json \ "hobbies").validate[JsValue]
} yield {
val maybeHobbyList = hobbies.validate[List[Hobby]].asOpt
Person(personName, maybeHobbyList.getOrElse(Nil))
}
}
}
implicit val personConverter = Format(personReads, Json.writes[Person])
}
The key thing to note here is surrounding the whole thing in a JsResult courtesy of the for-comprehension and the yield. This gives us all the necessary checking (like the name field being there and being a String, and the hobbies field being there).
The code within the yield block only runs if we've got something that looks pretty close to a Person. Then we can safely try validating the hobbies as a List[Hobby], and convert the result to an Option[List[Hobby]]. It'll be a None if it didn't work (thus it must have been a string) and so we default it to the empty list as required.
Thanks #millhouse answer, it definitely works. Like he said we need a custom Reads[Person] to properly convert it.
I also post my code as reference.
implicit val personJsonReads: Reads[Person] = (
(__ \ "name").read[String] and
(__ \ "hobbies").read[List[Hobby]].orElse(Reads.pure(List()))
) (Person.apply _)
read[List[Hobby]].orElse(Reads.pure(List())) will generate the empty list when the value cannot convert to List[Hobby].
I am trying to parse the nested Json and get the list of name from the fields tag.
{
"subject": "table-name",
"version": 1,
"id": 234,
"schema": "{
\"type\":\"record\",\"name\":\"table-name\",
\"fields\":[
{\"name\":\"NAME\",\"type\":\"CHARACTER\},
{\"name\":\"EID\",\"type\":\"INT\"},
{\"name\":\"DEPARTMENT\",\"type\":\"CHARACTER\"}
]
}"
}
I checked couple of post and came up with below code. I can get the schema definition(which is fairly easy) but not able to the list of name from fields.
case class Names(name : String)
case class FieldNames(fields: List[Names])
case class ColNames(subject: String, schema: FieldNames)
implicit val formats = DefaultFormats
val sourceSchema = parse("JsonStr").extract[ColNames]
println(sourceSchema)
My idea was the part schema: FieldNames will get me the fields tag and then List[Names] will get me the list of name but all I get is empty list. If I change the schema: FieldNames with schema: String I get the schema too but I am not getting how to get the required list of name after that. Below is the current output:
ColNames(table-name,FieldNames(List()))
Update:
I got it working but I ended up parsing the same Json twice which I don't feel is a best way of doing it. I would still like to know the best way to get it done. Below is my current solution:
case class Name(name : String)
case class FieldNames(fields: List[Name])
case class ColNames(subject: String, schema: String)
val sourceSchema = parse("JsonStr").extract[ColNames]
val cols= parse(sourceSchema.schema).extract[FieldNames]
Output:
FieldNames(List(Name(NAME), Name(EID), Name(DEPARTMENT)))