Converting json String into different objects at runtime based on their content - json

I'm invoking a web service that returns JSON.
The service returns either one of the following response.
case 1:
JSON:
[ {"name":"somevalue1", "key1":"value1", "key2":"value2"},
{"name":"somevalue1", "key1":"value1", "key2":"value2"},
{"name":"somevalue1", "key1":"value1", "key2":"value2"} ]
case class:
case class ValidResponse(name: String, key1: String, key2: String)
case 2:
JSON:
{"name": "invalid-response"}
Case class:
case class InvalidResponse(name:String)
I'm using json4s to parse the response as follows:
val parsedRes = parse(responseJson)
val objs: List[ValidResponse] = j.extract[List[ValidResponse]]
This works if the response string is the json in case 1. However, I get a parsedException in case the response string contains the json in case 2.
How can I handle response of multiple types?

A better way is to use one common class for both types of responses (valid and invalid):
case class Response(name: String, key1: Option[String], key2: Option[String]).
Play Framework has a great JSON parser. You can utilize this. Note that your Scala project does not have to be a Play project. You just need to import the library.
https://www.playframework.com/documentation/2.4.x/ScalaJson

Related

PlayJSON in Scala

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.

How to convert response body in Json using play framework

override def accessToken(): ServiceCall[RequestTokenLogIn, Done] = {
request=>
val a=request.oauth_token.get
val b=request.oauth_verifier.get
val url=s"https://api.twitter.com/oauth/access_token?oauth_token=$a&oauth_verifier=$b"
ws.url(url).withMethod("POST").get().map{
res=>
println(res.body)
}
The output which I am getting on terminal is
oauth_token=xxxxxxxxx&oauth_token_secret=xxxxxxx&user_id=xxxxxxxxx&screen_name=xxxxx
I want to convert this response in json format.like
{
oauth_token:"",
token_secret:"",
}
When Calling res.json.toString its not converting into jsValue.
Is there any other way or am I missing something?
According to the documentation twitter publishes, it seems that the response is not a valid json. Therefore you cannot convert it automagically.
As I see it you have 2 options, which you are not going to like. In both options you have to do string manipulations.
The first option, which I like less, is actually building the json:
print(s"""{ \n\t"${res.body.replace("=", "\": \"").replace("&", "\"\n\t\"")}" \n}""")
The second option, is to extract the variables into a case class, and let play-json build the json string for you:
case class TwitterAuthToken(oauth_token: String, oauth_token_secret: String, user_id: Long, screen_name: String)
object TwitterAuthToken {
implicit val format: OFormat[TwitterAuthToken] = Json.format[TwitterAuthToken]
}
val splitResponse = res.body.split('&').map(_.split('=')).map(pair => (pair(0), pair(1))).toMap
val twitterAuthToken = TwitterAuthToken(
oauth_token = splitResponse("oauth_token"),
oauth_token_secret = splitResponse("oauth_token_secret"),
user_id = splitResponse("user_id").toLong,
screen_name = splitResponse("screen_name")
)
print(Json.toJsObject(twitterAuthToken))
I'll note that Json.toJsObject(twitterAuthToken) returns JsObject, which you can serialize, and deserialize.
I am not familiar with any option to modify the delimiters of the json being parsed by play-json. Given an existing json you can manipulate the paths from the json into the case class. But that is not what you are seeking for.
I am not sure if it is requires, but in the second option you can define user_id as long, which is harder in the first option.

Play Json Parser: How to ignore field during json parser

I am using Scala with play JSON library for parsing JSON. We are the facing the problem using JSON parsing is, we have same JSON structure, but some JSON files contain different with values structure with the same key name. Let's take an example:
json-1
{
"id": "123456",
"name": "james",
"company": {
"name": "knoldus"
}
}
json-2
{
"id": "123456",
"name": "james",
"company": [
"knoldus"
]
}
my case classes
case class Company(name: String)
object Company{
implicit val _ = Json.format[Company]
}
case class User(id: String, name: String, company: Company)
object User{
implicit val _ = Json.format[Company]
}
while JSON contains company with JSON document, we are getting successfully parsing, but if company contains an array, we are getting parsing exception. Our requirements, are is there anyway, we can use play JSON library and ignore the fields if getting parsing error rather that, ignore whole JSON file. If I am getting, company array values, ignore company field and parse rest of them and map corresponding case class.
I would do a pre-parse function that will rename the 'bad' company.
See the tutorial for inspiration: Traversing-a-JsValue-structure
So your parsing will work, with this little change:
case class User(id: String, name: String, company: Option[Company])
The company needs to be an Option.
Final we found the answer to resolving this issue, as we know, we have different company structure within JSON, so what we need to do, we need to declare company as a JsValue because in any case, whatever the company structure is, it is easily assigned to JsValue type. After that, our requirements are, we need to use object structure, and if JSON contains array structure, ignore it. After that, we used pattern matching with our company JsValue type and one basis of success and failure, we parse or JSON. The solution with code is given below:
case class Company(name: String)
object Company{
implicit val _ = Json.format[Company]
}
case class User(id: String, name: String, company: JsValue)
object User{
implicit val _ = Json.format[Company]
}
Json.parse("{ --- whatevery json--string --- }").validate[User].asOpt match {
case Some(obj: JsObject) => obj.as[Company]
case _ => Company("no-name")
}

Play Framework Json Validation

I'm trying to parse a json structure, validate it and use the validated result.
def createEntry = Action(parse.tolerantJson) { request =>
request.body.validate[MyJson].map { myJson =>
// do something with the MyJson object
}.recoverTotal { err => BAD_REQUEST }
The MyJson object looks like this:
case class MyJson(
complexType: ComplexType,
strs: Seq[String],
maps: Map[String, ComplexType]
)
case class ComplexType(
str1: String,
bool1: Boolean,
cxType2: ComplexType2,
maps: Map[String, String]
)
case class ComplexType2(str: String, strs: Seq[String])
Would the validate method automatically try to box the JSON string into the object type? os Should I write an additional body parser?
Assuming you have the appropriate Format or Reads objects in scope for MyJson, yes, response.body.validate will automatically parse the JSON string into a MyJson instance, only entering the recoverTotal block if the JSON validation fails (i.e., the JSON in the request cannot be parsed into a MyJson, even if it is a valid JSON string).

Json Differences between angularjs and play framework

When i post from angularjs
{name:"John", age: 26}
i get BadRequest, however if is manually post
{"name":"John", "age": 26}
it works
in the Scala/Play side its the simple case class with Json formatting
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Customer(name: String, age: Int)
implicit val customerFormat = Json.format[Customer]
the Action is a simple one
def save = Action(parse.json) { request =>
request.body.validate[Customer].map { customer =>
myDAO.saveCustomer(customer)
Ok(toJson(customer))
}.getOrElse(BadRequest("invalid json"))
})
}
i guess the answer is either make angularjs quote the keys, or make play to ignore the the lack of keys, i'll need help on how do i do either of it, or am i missing something
In valid JSON, object keys must always be quoted. Try typing the object literal without the quotes into a JSON validator for confirmation.
It's important to note that there are differences between plain old Javascript object literals (POJOs) and JSON, with the JSON format being stricter. JSON is a string data type that happens to be valid Javascript. Technically, you obtain JSON data from Javascript code by stringifying a POJO:
JSON.stringify({name:"John", age: 26})
// "{"name":"John","age":26}"