I am trying to parse json using case class but running into an issue.
I have the following json
{
"general": {
"table": "123",
},
"employee" : {
"table": "employee_data"
},
"fulltime" : {
"table": "fulltime_employee_data"
},
"consultant" : {
"table": "consultant_employee_data"
}
}
Here's my case class:
case class EmployeeInfo(employees: List[Map[String, String]])
I'm trying to parse the above json using the case class using the following code. It returns the object as null.
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(new JavaTimeModule())
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val str = Source.fromFile("employeeInfo.json").mkString
val temp = mapper.readValue[EmployeeInfo](str)
temp here is being returned as null. My json seems to be list of maps which is what I provided in my case class. Any thoughts on what I'm missing?
I figured it out. My case class needed the variable name as defined in the json.
case class EmployeeInfo(general: TableDetails, employee: TableDetails, fulltime: TableDetails, consultant: TableDetails)
class TableDetails {
val table: String = ""
//getter and setter for table field
}
Related
Given incoming json like below, how can i decode it the given case class based on condition.
Incoming JSON
{
"config": {
"files": ["welcome"],
"channel": "media"
}
}
Case Classes
case class File(`type`: String, value: String)
case class Config(files: List[File],
channel: String = "BC")
object Config{
implicit val FileDecoder: Decoder[File] = deriveDecoder[File]
implicit val ConfigDecoder: Decoder[Config] = deriveDecoder[Config]
}
case class Inventory(config: Config)
object Inventory {
implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}
I do not have control on incoming files values in json it can be List[String] or List[File] so i need to handle both cases in my decoding logic.
So as we can see above my aim is to check if the incomes files values is List[String] then transform that values to below where type is hardcoded to "audio"
"files": [{
"type": "audio",
"value": "welcome.mp3"
}],
The overall json should look like below before it mapped into case classes for auto decoding.
{
"config": {
"files": [{
"type": "audio",
"value": "welcome.mp3"
}],
"channel": "media"
}
}
what i understood that this can be achieved either by transforming the json before decoding or can also be achieved during the decoding of files.
I tried writing the decoding logic at File level but i am not able to succeed. I am not getting the crux of how to do this.
Tried code
implicit val FileDecoder: Decoder[File] = deriveDecoder[File].prepare { (aCursor: ACursor) =>
{
if(!aCursor.values.contains("type")){
aCursor.values.map( v =>
Json.arr(
Json.fromFields(
Seq(
("type", Json.fromString("audio")),
("value", v.head)
)
)
)
)
}
}
}
We can use a custom Decoder for File to provide a default value to type
final case class File(`type`: String, value: String)
object File {
implicit final val FileDecoder: Decoder[File] =
Decoder.instance { cursor =>
(
cursor.getOrElse[String](k = "type")(fallback = "audio"),
cursor.get[String](k = "value")
).mapN(File.apply)
}.or(
Decoder[String].map(value => File(`type` = "audio", value))
)
}
Which can be used like this:
val data =
"""[
{
"type": "audio",
"value": "welcome.mp3"
},
{
"value": "foo.mp3"
},
"bar.mp3"
]"""
parser.decode[List[File]](data)
// res: Either[io.circe.Error, List[File]] =
// Right(List(
// File("audio", "welcome.mp3"),
// File("audio", "foo.mp3"),
// File("audio", "bar.mp3")
// ))
You can see the code running here.
I am trying to read below json structure using scala code but i am not able to read it as case class object.
"a": {
"source": [
{
"name": "test",
"basePath": "/Users/bounce/anuj/data/test",
"granularity": "None"
},
{
"name": "test1",
"basePath": "/Users/bounce/anuj/data/test1",
"granularity": "None"
}
]
}
}
Below is the code that i am using :
val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
val mapData = mapper.readValue(source, classOf[Map[String,FactConfig]])
#JsonInclude(Include.NON_EMPTY)
case class Source(name: String,
basePath: String,
granularity: String)
case class FactConfig(
source : List[Source])
val d = mapData.get("a").asInstanceOf[FactConfig]
When I m trying to convert it to FactConfig object I am getting below exception
Exception in thread "main" java.lang.ClassCastException: scala.collection.immutable.Map$Map1 cannot be cast to com.bounce.dp.etl.config.FactConfig
Please suggest to me what I am doing wrong.
In both Java and Scala world we normally lose generic type information during compilation. By using classOf operator jackson is not aware what should it do with map keys and values. The workaround is to use com.fasterxml.jackson.core.type.TypeReference, namely
val mapData = mapper.readValue(source, new TypeReference[Map[String, FactConfig]] {})
I have a file that contains the following array of JSON objects:
[
{
"type": "home",
"number": 1111
},
{
"type": "office",
"number": 2222
},
{
"type": "mobile",
"number": 3333
}
]
In Play Framework 2.x I would define an implicit Reads converter to read the file and convert it to a Scala structure:
implicit val implicitRead : Reads[MyClass] = (
(JsPath \ "type").read[String] and
(JsPath \ "number").read[Int]
) (MyClass.apply _)
the Scala case class defined as:
case class MyClass (myType: String, myNumber: Int)
and parsing the JSON with:
val json = // file record content
json.validate[MyClass] match {
case s: JsSuccess[MyClass] => {
val myObject: MyClass = s.get
// do something with myObject
}
case e: JsError => {
// error handling flow
}
Now, my problem is that I know the structure of the JSON file only at runtime, not at compilation time. Is it possible to build both the implicit Reads converter and the case class at runtime?
Use case classes directly with play-json:
Change the case class to:
case class MyClass (`type`: String, number: Int)
Add the json-formatter to the companion object:
object MyClass {
implicit val format = Json.format[MyClass]
}
The validate function looks now:
val myClass = // file record content
json.validate[Seq[MyClass]] match {
case JsSuccess(myClasses, _) => myClasses
case e: JsError => // handle error case
}
That's all you need. If you are not happy with the parameter names, you can use a Wrapper case class.
This might be a novice question, I really apologies for that but I wasn't able to find it any where. Code and output mentioned.
Using Play Framework 2.3.9 (Scala)
// controller
object Products extends Controller {
def list = Action {
Ok(Json.obj("products" -> Product.all))
}
}
// Model
case class Product(id: Long, name: Option[String])
object Product {
implicit val format: Format[Product] = Json.format[Product]
def all = DB.withConnection { implicit c =>
SQL("SELECT * FROM Products;").apply().map { row =>
Product(
id = row[Long]("id"),
name = row[Option[String]]("name")
)
}.toList
}
}
output generated:
{
"products": [{
"id": 1,
"name": "test"
}, {
"id": 2
}]
}
output wanted:
{
"products": [{
"id": 1,
"name": "test"
}, {
"id": 2,
"name": null
}]
}
What type of json value are you after for None? Do you mean a string "None", or the json null value null? At any rate, the formats line:
implicit val format: Format[Product] = Json.format[Product]
creates an automatic json formatter for you, but you can write your own to output the json in any custom format, e.g.:
implicit val productWrites = new Writes[Product] {
def writes(product:Product) = Json.obj(
"id" -> product.id,
"name" -> product.name
)
}
which will produce a value null (JsNull), i.e. "name" : null. for a product like Product(1,None). The following:
implicit val productWrites = new Writes[Product] {
def writes(product:Product) = Json.obj(
"id" -> product.id,
"name" -> JsString(product.name.getOrElse("None"))
)
}
would produce a json string value for name, i.e "name":"None". More info available from the relevant page on the play website.
What you're looking for is an implementation of Writes[Product] like so;
object Product {
implicit val writes = new Writes[Product] {
def writes(p: Product): JsValue = Json.obj(
"id" -> p.id,
"name" -> p.name // will output null and not None (as in your question)
)
}
}
{
"cars": [{
"amount": 120.00,
"name": "Car1"
}, {
"amount": 245.00,
"name": "Car2"
}]
}
I am reading above JSON as following in my Controller
val body: JsObject = request.body.asInstanceOf[JsObject]
I am having following CASE CLASS
case class BIC(name: String, amount: Double)
I want to create List[BIC] objects by reading data from JSON [e.g. body] using Functional style
Use Play JSON.
Example:
case class Wrapper(cars: List[Bic])
case class BIC(name: String, amount: Double)
Then in your controller:
implicit val wrapperFormats = Json.format[Wrapper]
implicit val bICFormats = Json.format[BIC]
def postCars(): Action[JsValue] = Action(json.parse) { implicit request =>
request.body.validate[Wrapper] match {
case JsSuccess(obj, _) => {
//do something with obj.
}
case JsError(err) => {
BadRequest(
JsObject(
"error" -> err.toString
)
)
}
}
}
Please note that I am returning Action[JsValue] this is so JQuery will run success when using AJAX.
I hope this helps,
Rhys
another reference:
https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators
First, define two case classes for your model like this :
object Models {
case class Bic(name : String, amount : Double)
object Bic {
implicit val BicFormat = Json.format[Bic]
}
case class Cars(bics : List[Bic])
object Cars {
implicit val CarsFormat = Json.format[Cars]
}
}
You're using the Play Framework so you can use the JSON library.
In your controller, if you want to read the bics, you can do it like that :
def getCars = Action(parse.json) { request =>
request.body.validate[Cars] map { cars =>
// treat your cars ..
}
}