Deserializing Json array into Scala object - json

I have been having major problems trying to deserialize a JSON array to a Scala object
[{"name":"Cool","city":"College Park","address":"806","id":1},{"name":"Mars ","city":"Durham","address":"12","id":2},{"name":"Something","city":"Raleigh
","address":"","id":3},{"name":"test","city":"","address":"","id":5}]
I have tried gson, jerkson(jackson scala wrapper), sjson, flexjson. None of them have worked. What I have here is a List of Customers. List[Customer].
This is the closest I've got:
val array = new JsonParser().parse( customers ).getAsJsonArray()
This gave me an 4 arrays. It obviously didn't give me a customer object though. I tried Jerkson.
val array = parse[List[Customer]](customers)
But I get this.
GenericSignatureFormatError occured : null
I'm just trying to find a simple way like I would in Java.
Here is my Scala class.
case class Customer(
id : Pk[ Int ],
name : String,
address : Option[ String ],
city : Option[ String ],
state : Option[ String ],
user_id : Int )
object Customer extends Magic[ Customer ]( Option( "Customer" ) ) {
def apply( name : String, address : String, city : String, state : String, user_id : Int ) = {
new Customer( NotAssigned, name, Some( address ), Some( city ), Some( state ), user_id )
}
def delete( id : Int ) = {
SQL( "DELETE from Customer where id = {id}" ).onParams( id ).executeUpdate()
}
}
Thanks for any help.

With gson, you could write your own json reader:
case class Customer(id: Int, name: String, address: Option[String],
city: Option[String], state: Option[String], user_id: Int)
object CustomerJsonReader {
def read(in: Reader) = readCustomers(new JsonReader(in))
def readCustomers(reader: JsonReader) = {
val customers = new ListBuffer[Customer]
reader.beginArray()
while (reader.hasNext()) {
customers += readCustomer(reader)
}
reader.endArray()
customers toList
}
def readCustomer(reader: JsonReader): Customer = {
var id = 0
var customerName = ""
var address: Option[String] = None
var city: Option[String] = None
var state: Option[String] = None
var userId = 0
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
name match {
case "id" => id = reader.nextInt()
case "address" => address = Some(reader.nextString())
case "state" => state = Some(reader.nextString())
case "user_id" => userId = reader.nextInt()
case "name" => customerName = reader.nextString()
case "city" => city = Some(reader.nextString())
case _ => reader.skipValue()
}
}
reader.endObject()
Customer(id, customerName, address, city, state, userId)
}
}
val json =
"""
[{"name":"Cool","city":"College Park","address":"806","id":1},
{"name":"Mars ","city":"Durham","address":"12","id":2},
{"name":"Something","city":"Raleigh ","address":"","id":3},
{"name":"test","city":"","address":"","id":5}]
"""
val customers: List[Customer] =
CustomerJsonReader.read(new StringReader(json))

I know that with gson, you would need Array instead of a scala.List. I would suggest giving that a shot. You should use that with gson.fromJson, I think.

You can also try Jerkson = Jackson + Scala
Quite easy to use even if I had problems with special JSON fields containing "-"
A small tuto I saw on twitter recently: http://logician.free.fr/index.php/2011/09/16/play-scala-and-json/

I've been driven insane by this now and went through trying GSON, Lift-Json, Sjson and finally Jerkson, and found peace with that one.
Here's how I use it in combination with Play:
http://logician.eu/index.php/2011/09/16/play-scala-and-json/
http://logician.eu/index.php/2011/11/01/writing-custom-deserializers-for-jerkson/

I use Lift's json library for this purpose, it easily lets you parse JSON and extract values into case classes. It's packaged as a separate jar so you don't need the whole lift framework to use it.
import net.liftweb.json._
import net.liftweb.json.JsonDSL._
implicit val formats = DefaultFormats
val json: String = "[{...."
val parsed: JValue = parse(json)
val customers: List[Customer] = parsed.extract[List[Customer]]
Just make sure any optional fields are defined in the case class using Option. I noticed in your code the objects are missing the user_id field, which would cause a parse error if the user_id field is declared as Int instead of Option[Int].

Aside from trying to make Jerkson (which is a great library to use from what I have heard), you could also try Jackson's Scala module -- modules are the official way Jackson is extended to deal with 3rd party datatypes as well as native datatypes and constructs of other JVM languages.
(this is not to say this is more official than Jerkson, just that there are many useful Jackson extension modules that many developers are not familiar with)
Issues with Scala module are discussed on main Jackson mailing lists (user#jackson.codehaus.org); you may have found an edge case that could be fixed.

I have written a parser/validator dsl, which allows you to explicitly resolve any type erasure issue. Out of the box it handles case classes, tuples, Option, Either, List, Map, joda DatetTime, piping to functions, multiple key mapping and key name remapping.
It uses the Jackson parser
https://github.com/HigherState/jameson

Its pretty simple to with scala and with play libraries and the code
import play.api.libs.json.{Format, Json}
case class Customer(name:String, city:String, address:String, id:Int)
object Customer {
implicit val jsonFormat: Format[Customer] = Json.format(Customer)
}
val jsonDef = Json.parse(<json-string>)
val customers = jsonDef.as[List[Customer]]
customers is list of Customer objects.

Related

How to get listitems in JSONObject?

my JSON is supposed to look like this.
{"zip":123, "people":[{"firstname":"Thomas", "lastname":"Tatum"},
{"firstname":"Drew", "lastname":"Uncle"}]}
(I am using import org.json.JSONObject)
I have a MutableList, in the List are Person (it’s a data class with firstname and lastname).
But I don’t know how to get my list items in a JSONObject to fit in json (see below).
val json = JSONObject(
mapOf(
"zip" to 123,
"people" to //I don't know how to get my values here
)
)
Maybe someone can help me.
You could do this
import org.json.JSONObject
data class Person(val firstname: String, val lastname: String)
fun main() {
val people = arrayOf(Person("Thomas", "Tatum"), Person("Drew", "Uncle")) //also works for Lists, doesn't need to be an array
val json = JSONObject(
mapOf(
"zip" to 123,
"people" to people,
)
)
println(json)
//prints: {"zip":123,"people":[{"firstname":"Thomas","lastname":"Tatum"},{"firstname":"Drew","lastname":"Uncle"}]}
}

Format an ISODate form field in ReactiveMongo Play JSON

Using MongoDB with Play Framework and the ReactiveMongo Play JSON library; I am trying to correctly configure a form. I have some dates stored in MongoDB which are in an ISODate wrapper/monad and handled as a JSObject:
Json.obj("dateOfBirth" -> Json.obj("$date" -> dateTimeLong))
An example of my case class using automated mapping in Play is:
case class FormData(_id: Option[BSONObjectID], name: String, dateOfBirth: Option[JSObject])
This is the form:
object MyForm {
val form = Form(
mapping(
"_id" -> ignored(Option.empty[BSONObjectID]),
"name" -> nonEmptyText,
"dateofBirth" -> ? // not `date` or `optional(date)`
)
)
}
I think that I need to custom bind the dateOfBirth field in the form but this is where I am having difficulty. I couldn't find where to include an implicit object to format a new type as shown here and this also didn't help me with this (and looks like it might be outdated by now).
I think that the bind method might look something like this:
def bind(date: Date, dateFieldName: String): JsObject = {
val longDate = new DateTime(date.getTime())
.withZoneRetainFields(DateTimeZone.UTC)
.withZone(DateTimeZone.getDefault())
.getMillis
Json.obj(s"$$$dateFieldName" -> JsNumber(longDate))
}
But I could be wrong and it would be nice to try however as I say I can't seem to find where the correct place to insert a custom formatter - I thought this should be in the companion object of FormData (where formats would be for nested fields). This wasn't the case so I am approaching the community once more. Thanks for any suggestions - particularly if there is a better way to handle this.
I knew that the answer was out there but it definitely shouldn't have been so well hidden! This one is for anyone that is feeling my pain. So I changed the following:
case class FormData(_id: Option[BSONObjectID], name: String, dateOfBirth: Option[Date])
And the form..
object MyForm {
val form = Form(
mapping(
"_id" -> ignored(Option.empty[BSONObjectID]),
"name" -> nonEmptyText,
"dateOfBirth" -> optional(date)
)(FormData.apply)(FormData.unapply)
)
}
And now the critical part. Within the companion object of the case class (mine is FormData) is where to put the following (this was altered from Stephane Godbillon's answer here):
implicit val dateRead: Reads[Date] = (__ \ "$date").read[Long].map { date =>
new Date(date)
}
implicit val dateWrite: Writes[Date] = new Writes[Date] {
def writes(date: Date): JsValue = Json.obj(
"$date" -> date.getTime
)
}

Akka HTTP Json Marshalling

I'm building simple REST API using Akka-HTTP and MongoDB for persistence. For Marshalling and Unmarshalling HTTP requests I'm using spray json. Following MongoDB documentation I defined entities like that:
package entities
import org.mongodb.scala.bson.ObjectId
case class Person(
_id: ObjectId,
firstName: String,
lastName: String,
email: String,
emailConfirmed: Boolean,
telephone: String,
...
)
object Person {
def apply(
firstName: String,
lastName: String,
email: String,
telephone: String,
...
): Publisher = new Publisher(
new ObjectId(),
firstName,
lastName,
email,
false,
telephone,
...
)
}
The problem with this approach is that I have to write a lot of boilerplate code to enable Marshalling-Unmarshalling:
implicit object PublisherItemFormat extends RootJsonFormat[Publisher] {
def write(publisher: Publisher) = JsObject(
"_id" -> JsString(publisher._id.toString),
"firstName" -> JsString(publisher.firstName),
"lastName" -> JsString(publisher.lastName),
"email" -> JsString(publisher.email),
"telephone" -> JsString(publisher.telephone),
...
)
def read(json: JsValue) = {
val jsObject = json.asJsObject
jsObject.getFields(
"_id",
"firstName",
"lastName",
"email",
"telephone",
...
) match {
case Seq(
_id,
firstName,
lastName,
email,
telephone,
...
) ⇒ Publisher(
firstName.convertTo[String],
lastName.convertTo[String],
email.convertTo[String],
telephone.convertTo[String],
...
)
}
}
}
I have approx 10 different entities that have to be saved in the Mongo, and each of them have 5-15 fields. With this number of entities, this kind of approach will produce too much boilerplate.
What could be a better way of doing it, with less boilerplate ? May-be there is another Json-serialization library instead of "spray-json" which can figure things out automatically with less code ?
Or how can I define Marshalling class for org.mongodb.scala.bson.ObjectId using Spray-Json ? This will also help to get rid of boilerplate code.
Use automatic derivation:
object EntitesJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
implicit val placeResponseFormat = jsonFormat19(Person.apply)
def personToJson(p: Person) = p.toJson
}
the personToJson method is implemented so you don't have to contaminate your code with json imports, note that the order of declaration is important if a person is embedded inside a publisher you will have to declare personFormat before publisherFormat

Scala : Converting object having UUID field to Json returns blank

Here is my case class that i want to convert to Json
case class Cart(cart_id :UUID, cart_entries :Map[String,CartEntry]){
}
I am using net.liftweb.json._
implicit val formats = UUID
val json = write(cart) //cart is Cart object with values for both attributes cart_id = 68eb787f-746c-4320-9ef4-8b5c7f0d7e21
println(json)
the json returns somthing like :
{"cart_id":{},"cart_entries":[{"_1":"ABC","_2":{"sku_id":"ABC","quantity":12,"price":{"bigDecimal":{},"mc":{}}}}]}
notice the value for cart_id is blank {} , I expect something like :
{"cart_id":{68eb787f-746c-4320-9ef4-8b5c7f0d7e21},"cart_entries":[{"_1":"ABC","_2":{"sku_id":"ABC","quantity":12,"price":{"bigDecimal":{},"mc":{}}}}]}
I have used other api's like fasterxml all return "" for UUID .
How do i fix this ?
While your Cart is a case class composed of types for which lift-json provides serializers, the UUID class probably isn't (I assume you use java.util.UUID). Therefore you need to write your own serializer & deserializer, with something like this (untested):
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: UUID => JString(x.toString)
}

Error in json serialization in Scala with Play2

i have following three case classes
case class Delete(var deleteStatus : DeleteStatus , var deleteReason : DeleteReason) // DeleteStatus and DeleteReason are enums
case class Message(val uuid: Int ,val subject : String, val body : String, var awt : Int,val dateTime : LocalDateTime = LocalDateTime.now(), delete : Delete)
case class Inbox( val uuid : Int,var messageList : ListBuffer[Message] )
i want to serialize them to Json and but i am not sure how should i do this
i have tried it like this
def writedelete(delete: Delete) = Json.obj(
"deleteStatus" -> delete.getDeleteStatusInt.toString,
"deleteReason" -> delete.getDeleteReasonInt.toString
)
def writeMessage(mgs : Message)= Json.obj(
"uuid" -> mgs.getUuid ,
"subject" -> mgs.getSubject,
"body" -> mgs.getBody,
"awt" -> mgs.getAwt,
"datetime" -> mgs.getdateTime.toString,
"delete" -> mgs.delete
)
def writeInbox(inbox : Inbox)= Json.obj(
"uuid" -> inbox.getUuid,
"mgslist" -> Seq(inbox.getMessageList)
)
but it gives following error on mgs.delete in writeMessage and mgslist in writeInbox
type mismatch; found : models.UserNotifications.MailMessages.Delete
required: play.api.libs.json.Json.JsValueWrapper
type mismatch; found :
Seq[scala.collection.mutable.ListBuffer[models.UserNotifications.MailMessages.Message]]
required: play.api.libs.json.Json.JsValueWrapper
please guide me how can i get get rid off it
and also is there any better way of doing this?
When you use Json.obj(...) to construct a JSON object various implicit conversions are available to convert common types (e.g. String, Int) to their JSON wrapper types (JsString, JsNumber). The problem with your code is there are no implicit conversions available to convert your Delete and Message types to JSON. One option would be to use your explicit conversion functions directly, e.g:
"delete" -> writeDelete(mgs.delete)
and (using Json.arr(...) to construct a JSON array):
"msglist" -> Json.arr(inbox.getMessageList.toSeq.map(writeMessage): _*)
However, the more idiomatic way to do this would be to use the JSON Inception macros to automatically generate (implicit) serializers for your types.
Simplifying slightly, this would look something like this:
case class Delete(deleteStatus: DeleteStatus, deleteReason: DeleteReason)
object Delete {
implicit val _format = Json.format[Delete]
}
case class Message(uuid: Int, subject: String, body: String, awt: Int, dateTime: LocalDateTime, delete: Delete)
object Message {
implicit val _format = Json.format[Message]
}
case class Inbox(uuid: Int, messageList: ListBuffer[Message])
object Inbox {
implicit val _format = Json.format[Message]
}
You should now be able to automatically serialize (and de-serialize) your Delete, Message, and Inbox objects using Json.toJson(thing) because it will find an implicit Format object (a combined Reads and Writes) on the companion object of each custom type.
One complication here is that your case classes contain enums; if they're Scala enumerations see this answer for how to convert them. I leave that as an exercise for the reader.