Issue converting decimal (Double) value from JSON to case class - java

I am using Scala 2.12 with circe version 0.14.1. I am converting a JSON into corresponding case class as below:
case class Document(curr: String, value: Double)
val json = s"""{"Document":{"curr":"USD","value":40000000.01}}"""
import io.circe.generic.auto._
io.circe.parser.decode[Document](json)
The converted case class is below:
Document(USD,4.000000001E7)
I do not want the Double value to change into a Exponential representation. I need it to remain as Double unchanged as
40000000.01
.

You can override toString implementation to see pain instead of engineering notation:
case class Document(curr: String, value: Double) {
override def toString: String =
s"Document($curr,${BigDecimal(value).underlying().toPlainString})"
}
If you want JSON serialization in the plain notation then use the following encoder:
implicit val doubleE5r: Encoder[Double] = new Encoder[Double] {
override def apply(a: Double): Json =
Json.fromJsonNumber(JsonNumber.fromDecimalStringUnsafe(BigDecimal(a).underlying().toPlainString))
}

Related

Custom serialization and deserialization is scala using jackson

I have a JSON as a string which I am deserializing and instantiating as MyPOJO case class of scala. My data is in YYYY-MM-DD format but the actual attribute in POJO createdBy is LocalDateTime.
How to assign a default time value of 2020-03-02 00:00:00 while instantiating Pojo,
Serialization should return yyyy-mm-dd format. My serialization and deserialization format are different.
case class MyPOJO( #JsonFormat(pattern = "yyyy-mm-dd" ) createdBy :LocalDateTime )
object MyJaxsonP {
def main(args: Array[String]): Unit = {
val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.findAndRegisterModules()
objectMapper.registerModule(DefaultScalaModule)
objectMapper.registerModule(new JavaTimeModule)
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
val adminDatasP = objectMapper.readValue[MyPOJO]("{\"createdBy\":\"2020-03-02\"}")
print(adminDatasP.toString)
}
}
I have tried with custom serialization and deserialization, like below, but is not working saying default constructor is missing
case class MyPOJO( #JsonDeserialize(using = classOf[CustomDateDeserializer] ) createdBy :LocalDateTime )
object CustomDateDeserializer {
private val formatter = new SimpleDateFormat("dd-MM-yyyy")
}
class CustomDateDeserializer(val t: Class[String]) extends StdDeserializer[String](t) {
override def deserialize(p: JsonParser, ctxt: DeserializationContext): String = {
val date = p.getText
return CustomDateDeserializer.formatter.format(date);
}
}
Need expert input on how to solve this problem
but is not working saying default constructor is missing
You getting the error, because there is not default or empty, if you will, constructor for case classes. In this particular case when you declare case class MyPOJO(createdBy : LocalDateTime) scala compiler will generate something like (example might be not very accurate, I want just to show an idea):
class MyPOJO(createdBy : LocalDateTime) extends Product with Serializeble {
override def hashCode(): Int = ...
override def equals(oterh: Any): Boolean = ...
override def toString(): String = ...
// and other methods, like copy, unaply, tupled etc.
}
object MyPOJO {
def apply(createdBy : LocalDateTime) = new MyPOJO(createdBy)
}
so Jackson wont be able to create empty class instance with empty fields (or null values more precisely) and then inject values from source JSON.
What you can do - use plain class instead of case classes. Or, what is more preferable, to take a look on Scala friendly JSON libraries like Circe which is not reflection based, unlike Jackson, and instead generates JSON codec in compile time for some classes, based on implicit's and Scala macros (more precisely it relies on Shapeless library which uses Scala macros mechanism under the hood).
In your particular case, code would look like:
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
case class MyPOJO(createdBy: LocalDateTime)
val format = DateTimeFormatter.ofPattern("yyyy-MM-dd")
// Implement own `Encoder` to render `LocalDateTime` as JSON string, separated with comma inside
implicit val encoder: Encoder[LocalDateTime] = Encoder[String].contramap(_.format(format))
// Implement own `Decoder` to parse JSON string as `LocalDateTime`
implicit val decoder: Decoder[LocalDateTime] = Decoder[String].
emapTry(value => Try(LocalDate.parse(value, format).atStartOfDay()))
val foo = MyPOJO(LocalDateTime.now())
val json = foo.asJson
println(json.noSpaces)
println(json.as[MyPOJO])
which will produce next result:
{"createdBy":"2020-03-04"}
Right(MyPOJO(2020-03-04T00:00))
Hope this helps!

How can I obtain the default value of a constructor parameter for a non-case class?

class Person(name: String, age: Int, numThings: Option[Int] = Some(15))
I can use Scala reflection to obtain defaults on a case class like this:
val companionType: Type = classSymbol.companion.typeSignature
val companionObject = currentMirror.reflectModule(classSymbol.companion.asModule).instance
val companionMirror = currentMirror.reflect(companionObject)
val defaultValueAccessorMirror =
if (member.typeSignature.typeSymbol.isClass) {
val defaultValueAccessor = companionType.member(TermName("apply$default$" + (index + 1)))
if (defaultValueAccessor.isMethod) {
Some(companionMirror.reflectMethod(defaultValueAccessor.asMethod))
} else {
None
}
} else {
None
}
This obtains the method in the generated companion object that, when called, coughs up the default value. Sadly, a non-case class doesn't appear to have this facility.
How can I obtain the default value for Person.numThings in the example above using either Scala or Java reflection?
I think that it should be much easier to retrieve these default values through Java reflection, instead of this over-complicated Scala's reflect...
When compiled into a .class file, default parameter values are translated into static methods with specific suffixes in names. These values can be retrieved by invoking the respective method on the class reference.
So, for example, we have both a case and a non-case classes:
class Person(name: String, age: Int, numThings: Option[Int] = Some(15))
case class Item(id: Long, other: String = "unknown")
First we need to determine the ordinal indices of the params to retrieve defaults for. I do not know your use case, so let's suppose you know or calculated them. They will be 3 for Person and 2 for Item. Yes, they are not 0-based.
And this very short method retrieves the values:
private def extractDefaultConstructorParamValue(clazz: Class[_],
iParam: Int): Any = {
val methodName = "$lessinit$greater$default$" + iParam
clazz.getMethod(methodName).invoke(clazz)
}
Calling them with
val defParamNonCase = extractDefaultConstructorParamValue(classOf[Person], 3)
val defParamCase = extractDefaultConstructorParamValue(classOf[Item], 2)
println(defParamNonCase)
println(defParamCase)
outputs
Some(15)
unknown

Gson-like library for scala

I'm learning scala. I'm trying to find an easy way for turing JSON String to Scala case class instance. Java has wonderful library called Google Gson. It can turn java bean to json and back without some special coding, basically you can do it in a single line of code.
public class Example{
private String firstField
private Integer secondIntField
//constructor
//getters/setters here
}
//Bean instance to Json string
String exampleAsJson = new Gson().toJson(new Example("hehe", 42))
//String to Bean instance
Example exampleFromJson = new Gson().fromJson(exampleAsJson, Example.class)
I'm reading about https://www.playframework.com/documentation/2.5.x/ScalaJson and can't get the idea: why it's so complex is scala? Why should I write readers/writers to serialize/deserialize plain simple case class instances? Is there easy way to convert case class instance -> json -> case class instance using play json api?
Let's say you have
case class Foo(a: String, b: String)
You can easily write a formatter for this in Play by doing
implicit val fooFormat = Json.format[Foo]
This will allow you to both serialize and deserialize to JSON.
val foo = Foo("1","2")
val js = Json.toJson(foo)(fooFormat) // Only include the specific format if it's not in scope.
val fooBack = js.as[Foo] // Now you have foo back!
Check out uPickle
Here's a small example:
case class Example(firstField: String, secondIntField: Int)
val ex = Example("Hello", 3)
write(ex) // { "firstField": "Hello", "secondIntField" : 3 }

Why does scalac take the Java vararg method instead of the single argument

We're using Elasticsearch as database and based upon a definition I'm creating a type mapping.
This mapping is basically a JSON object which gets built with the XContentBuilder of elasticsearch Java-API.
In my scala file I've defined an Enumeration object that holds the possible elasticsearch data-types like this:
object TypeMapping extends Enumeration {
val StringType = DataType("string")
val FloatType = DataType("float")
...
val GeoShapeType = DataType("geo_shape")
val AttachmentType = DataType("attachment")
final case class DataType(esType: String) extends Val {
override def toString: String = esType
}
}
Now when I use this in the creation of the mapping JSON like this:
val builder = jsonBuilder.startObject("name").field("type", StringType).endObject
the scala compiler can nicely resolves all the methods to call; no errors or warnings.
The method field is overloaded, each receiving a String parameter name and a parameter value. These values can be specific (String, int, int[], etc.) or vararg (String..., int..., etc.) but there's also an Object variant for both specific and vararg calls.
Now I would expect that the scala compiler would choose the field(String name, Object value) in the case I'm describing here, but to my suprise I find that the field(String name, Object... value) is being called.
I do not understand why this is happening. Can anybody explain this to me?
Scala picks the varargs version as more specific because (String, Array[Any]) can be applied to the other signature field(name: String, value: Any). (And not vice-versa.)
Given that both methods are in the same class, I'm not sure if there is a canonical workaround besides reflective access:
type Picker = {
def f(name: String, value: Any): Int
}
Console println x.f("hi", "high") // varargs
Console println (x: Picker).f("hi", "high") // not
Disambiguating:
public class JOver {
public int f(String name, Object value) { return 1; }
public int f(String name, Object... values) { return 2; }
}

Object Autoconvert to Double with Serialization/GSON

I ran into a problem when developing an application that uses Gson to serialize objects and deserialize them. However, I ran into a problem that I cannot explain the cause of and after a while, I narrowed down the problem to this SSCCE:
import com.google.gson.Gson;
/**
* demonstrates the issue at hand
*/
public class Probs {
public Probs () {
//holds the byte array form of the JSON data
byte[] info = new byte[1];
//get the JSON for a data object and store it in the byte array
Gson gson = new Gson();
Data before = new Data(1);
info = gson.toJson(before).getBytes();
//reassemble the JSON data as a string
String json = new String(info);
System.out.println("JSON string: " + json);
//reconstruct the Data object from the JSON data
Data after = gson.fromJson(json, Data.class);
//attempt to get the "num" value and convert it to an integer
Object val = after.getNum();
System.out.println("Class name: " + val.getClass().getName()); //is java.lang.Double (why isn't it java.lang.Object?)
Integer num = (Integer)val; //produces "java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer"
System.out.println("Number: " + num);
}
public static void main(String[] args) {
new Probs();
}
}
/**
* holds the one piece of data
*/
class Data {
Object num;
public Data(int num) {
this.num = num;
System.out.println("Object value: " + this.num);
}
public Object getNum () {
return this.num;
}
}
I did read this post but it did not appear to have any accepted answers. Because of the way I use it in my application, I need to have the Data object store its data as an Object and be able to cast it later to a different type. When I deserialize the data object and call its getNum(), I thought that should return an Object (since that is its return type). In my application, I need to be able to convert that type into an Integer. However, the JVM appears to convert the Object (val) into a Double because the getClass() reveals that it is a Double and not an Object. Then when I try to convert it to an integer via a cast it fails because it is apparently a Double and not an Object.
My question is: why is val a Double and not an Object (what am I not understanding)?
Thank you for your help
The issue is the JSON spec, and what you're doing.
The JSON spec only specifies a single numeric type, which can a include a decimal point and a fractional portion:
2.4. Numbers
The representation of numbers is similar to that used in most
programming languages. A number contains an integer component that
may be prefixed with an optional minus sign, which may be followed by
a fraction part and/or an exponent part.
JSON parsers are left to decide for themselves what to do with that numeric type when parsing/mapping the JSON.
In your case, your Data class has num defined as Object. This gives Gson no hint as to what specific Java numeric type you'd like the JSON numeric type mapped to. The authors of Gson decided to use a Double when this is the case regardless of whether the number in the JSON includes a decimal + fraction or not.
This actually makes perfect sense when you consider that an integer can be expressed as a double, but not the other way around. Using a single type rather than parsing the number and deciding if it's a int or a double provides consistent behavior.
It's unclear why you aren't using Integer (or int) for num in your Data object if that's what you expect/need. You state you need to cast to Integer "later" which means the only thing that object can be in the first place is an Integer; any other casting attempt would fail.

Categories