Let's say I have the next case class:
case class Person(id: String, money: BigDecimal)
object Person {
implicit val encoder: Encoder[Person] = Encoder.forProduct2("ID", "Money")(u =>
(u.id, u.money))
I want to serialize instances of the Person class to JSON, so when I evaluate the asJson from circe, I get the result in scientific notation:
{
"ID" : "123",
"VALOR_SAP" : 2.7E+7
}
Why do this happens? I think the reason is because the default to string of BigDecimal automatically format to scientific notation.
What could I do to avoid this? May be creating another type which extends from BigDecimal and overriding the toString?
I assume that you use scala.math.BigDecimal, for java.math.BigDecimal code is similar. The way to change how objects are serialized is to provide corresponding implicit Encoder object. Unfortunately both Json and JsonNumber hierachies are sealed, so there is no very clean solution but you still can use JsonNumber.fromDecimalStringUnsafe that implements toString to just return any string you passed in. So you can do something like this:
case class Person(id: String, money: BigDecimal)
object Person {
implicit final val bigDecimalAsPlainStringEncoder: Encoder[BigDecimal] = new Encoder[BigDecimal] {
final def apply(value: BigDecimal): Json = Json.fromJsonNumber(JsonNumber.fromDecimalStringUnsafe(value.bigDecimal.toPlainString))
}
implicit val encoder: Encoder[Person] = Encoder.forProduct2("ID", "Money")(u => (u.id, u.money))
}
Related
I'm trying to write a method that will allow Jackson ObjectMapper readValue on a json string to a parameterized object type. Something like this
case class MyObj(field1: String, field2: String)
val objectMapper: ObjectMapper = new ObjectMapper().registerModule(new DefaultScalaModule)
def fromJson[T](jsonString: String, objTyp: T): T = {
objectMapper.readValue(jsonString, classOf[T])
}
val x = fromJson("""{"field1": "something", "field2": "something"}""", MyObj)
This of course returns an error of
class type required but T found
i've looked at this issue Scala classOf for type parameter
but it doesn't seem to help. It seems like this is possible to do somehow. Looking for any help
You have to give it the actual runtime class to parse into, not just a type parameter.
One way to do it is passing the class directly:
def fromJson[T](json: String, clazz: Class[T]) = objectMapper.readValue[T](json, clazz)
val x = fromJson("""...""", classOf[MyObj])
Alternatively, you can use ClassTag, which looks a bit messier in implementation, but kinda prettier at call site:
def fromJson[T : ClassTag](json: String): T = objectMapper.readValue[T](
json,
implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]
)
val x = fromJson[MyObj]("""{"field1": "something", "field2": "something"}""")
i've looked at this issue Scala classOf for type parameter but it doesn't seem to help.
In the very first answer there it's written classTag[T].runtimeClass as a replacement of classOf[T]. This should help.
Regarding the signature
def fromJson[T](jsonString: String, objTyp: T): T
You should notice that MyObj has type MyObj.type (companion-object type), not MyObj (case-class type).
Class companion object vs. case class itself
So if you call fromJson("""...""", MyObj) then the types in these two places
def fromJson[...](jsonString: String, objTyp: ???): ???
^^^ ^^^ <--- HERE
can't be the same.
If it's enough for you to call
fromJson("""...""", classOf[MyObj])
or
fromJson[MyObj]("""...""")
(normally it should be enough) then please see #Dima's answer, you should prefer those options, they're easier.
Just in case, if you really want to call like fromJson("""...""", MyObj) then for example you can use the type class ToCompanion (this is more complicated) from
Invoke construcotr based on passed parameter
Get companion object of class by given generic type Scala (answer)
// ToCompanion should be defined in a different subproject
def fromJson[C, T](jsonString: String, objTyp: C)(implicit
toCompanion: ToCompanion.Aux[C, T],
classTag: ClassTag[T]
): T =
objectMapper.readValue(jsonString, classTag.runtimeClass.asInstanceOf[Class[T]])
val x = fromJson("""{"field1": "something", "field2": "something"}""", MyObj)
// MyObj(something,something)
How to configure the spray-json parsing on parsing options?
Similarly as Jackson Parsing Features.
For example, I am parsing a json that has a field that my case class has not, and it is breaking:
spray.json.DeserializationException: Object is missing required member 'myfield'
UPDATE :
A simple example:
case class MyClass(a: String, b: Long);
and try to parse an incomplete json like
val data = "{a: \"hi\"}"
with a spray-json format like:
jsonFormat2(MyClass.apply)
// ...
data.parseJson.convertTo[MyClass]
(simplified code).
But the question goes further, I want to ask about configuration options like in other parsers. More examples:
Be able to ignore fields that exist in the JSON but not in the case class.
Ways of managing nulls or nonexistent values.
etc.
SprayJson allows you to define custom parsers like so:
case class Foo(a: String, b: Int)
implicit object FooJsonFormat extends RootJsonFormat[Foo] {
override def read(json: JsValue): Foo = {
json.asJsObject.getFields("name", "id") match {
case Seq(JsString(name), id) =>
Foo(name, id.convertTo[Int])
}
}
override def write(obj: Foo): JsValue = obj.toJson
}
This allows you to parse any arbitrary payload and pull out the fields "name" and "id" - other fields are ignored. If those fields are not guaranteed you can add something like:
case Seq(JsString(name), JsNull) =>
Foo(name, 0)
You should look at what's available in JsValue.scala - in particular JsArray may come in handy if you're getting payloads with anonymous arrays (i.e. the root is [{...}] instead of {"field":"value"...})
Spray Json doesn't support default parameters. So You cannot have a case class like
case class MyClass(a: String, b: Int = 0)
and then parse json like {"a":"foo"}
However if you make the second parameter as Option. then it works.
import spray.json._
case class MyClass(a: String, b: Option[Int] = None)
object MyProtocol extends DefaultJsonProtocol {
implicit val f = jsonFormat2(MyClass)
}
import MyProtocol.f
val mc1 = MyClass("foo", Some(10))
val strJson = mc1.toJson.toString
val strJson2 = """{"a": "foo"}"""
val mc2 = strJson2.parseJson.convertTo[MyClass]
println(mc2)
I have a Person case class:
case class Person(name: String, createdAt: LocalDateTime)
to be able to serialize person object to json so I can return it to the user
I have a serualizer:
object PersonSerializer {
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
and I import this serializer in the controller so when I can return the result to the use as json like this:
def getPeople: Action[AnyContent] = Action.async {
peopleDao.getAllPeople.map(people => Ok(Json.toJson(res)))
}
BUT, I get this error:
Error:(39, 55) No instance of play.api.libs.json.Format is available
for org.joda.time.LocalDateTime in the implicit scope (Hint: if
declared in the same file, make sure it's declared before) implicit
val AFormat: OFormat[Account] = Json.format[Account]
How can I fix this?
Your answer is pretty much in the stacktrace. Basically, in order to format a Person, Play's serializer needs to know how to serialize a LocalDateTime. You should try something like:
object PersonSerializer {
implicit val LocalDateFormat: OFormat[LocalDateFormat] =
new OFormat[LocalDateFormat](){ /*...*/ }
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
I suggest you to look at this post, this one, and the documentation.
Another option is using of jsoniter-scala: https://github.com/plokhotnyuk/jsoniter-scala
You will get build in support of java.time.* classes with more than 10x times greater throughput for parsing & serialisation.
Just see results of ArrayOfLocalDateFormatBenchmark for Jsoniter-scala vs. Circe, Jackson and Play-JSON: http://jmh.morethan.io/?source=https://plokhotnyuk.github.io/jsoniter-scala/oraclejdk8.json
I have the following use-case:
Each class that I'm serde using JSON4S have a field, named ID. This ID can be any type T <: Stringifiable, where Stringifiable requires your ID type to be hashed to a string. Stringifiables also have constructors that rebuilds them from a string.
I'd like to serde any Stringifiable, for example ComplexIdentifier to a JSON of ID: stringified_identifier. Serialization works nicely, but unfortunately during deserialization, JSON4S is not going to use the default constructor which has only 1 string constructor. It finds the constructor, but if the identifier has a signature of case class ComplexIdentifier(whatever: String), it tries to extract a whatever name from the JString(stringified_identifier). That fails, so MappingException is thrown internally.
Is there any way to teach JSON4S to use the default constructor without extracting the values like this? It would be so obvious to just use the value from the JString and construct the Stringifiable using that.
Thanks!
Use the applymethod in a Companion to overload the constructor for the ID classes with a String parameter. Then just use a custom serializer for all of your ID types
sealed abstract class Stringifiable {}
case class ComplexIdentifier(whatever: List[Long]) extends Stringifiable
case class SimpleIdentifier(whatever: Int) extends Stringifiable
//Overload the default constructor
object ComplexIdentifier {
def apply(s: String):ComplexIdentifier = {
ComplexIdentifier(s.split(",").map(_.toLong).toList)
}
}
case class MyClass(id: ComplexIdentifier, value: String)
Then use a custom serializer:
case object ComplexIdentifierSerializer extends CustomSerializer[ComplexIdentifier] ( formats =>
({
case JString(id) => ComplexIdentifier(id)
case JNull => null
},
{
case x: ComplexIdentifier => JString(x.whatever.mkString(","))
}))
Finally, make sure to include the serializer in the implicit formats:
implicit val formats = DefaultFormats ++ List(ComplexIdentifierSerializer)
println(parse("""
{
"id": "1",
"value": "big value"
}
""").extract[MyClass])
val c = MyClass(ComplexIdentifier("123,456"), "super value")
println(write(c))
I am using Spray-json 1.3.1. I have the following JSON message:
{
"results": [{
... NOT IMPORTANT PART HERE ...
}],
"status": "OK"
}
Trivially, this can be deserialized to status String field via
case class Message[T](results: List[T], status: String)
with custom Protocol
object MessageProtocol extends DefaultJsonProtocol {
implicit def messageFormat[T: JsonFormat] = jsonFormat2(Message.apply[T])
}
Since status field can be one of OK, ZERO_RESULTS, OVER_QUERY_LIMIT having this field as a String makes no sense. As I am coming from
Java background I tried enums in Scala implemented as follows:
case class Message[T](results: List[T], status: Status)
object Status extends Enumeration{
type Status = Value
val OK,ZERO_RESULTS,OVER_QUERY_LIMIT, REQUEST_DENIED, INVALID_REQUEST,UNKNOWN_ERROR = Value
}
object MessageProtocol extends DefaultJsonProtocol {
implicit val statusFormat = jsonFormat(Status)
implicit def messageFormat[T: JsonFormat] = jsonFormat2(Message.apply[T])
}
What is best practice/approach to solve this?
You can simply implement your own RootJsonFormat (as an implicit in Message companion object) and override read and write functions. There you will have JsObject and you can convert it to your own case class as you want like converting the string to desired enumeration etc. You can see a sample here