set Jackson ObjectMapper class not to use scientific notation for double - java

I am using a library com.fasterxml.jackson library for JsonSchema,
I am creating an IntegerSchema object, when I set range for integer schema using below code:
main(){
IntegerSchema intSchema = new IntegerSchema();
// setMaximum accepts Double object
intSchema.setMaximum(new Double(102000000));
// setMaximum accepts Double object
intSchema.setMinimum(new Double(100));
printJsonSchema(intSchema);
}
public void printJsonSchema(JsonSchema schema){
ObjectMapper mapper = new ObjectMapper();
try {
logger.info(mapper.writeValueAsString(schema));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}
When I convert IntegerSchema to string using ObjectMapper getting below response:
{"type":"integer","maximum":1.02E8,"minimum":100.0}
maximum and minimum values are getting converted to scientific notation.
But I need output in non scientific notation as below:
{"type":"integer","maximum":102000000,"minimum":100}
I cannot change IntegerSchema class.
Please suggest how to get the required output without extending IntegerSchema class?
Thanks in advance

this is a java issue somewhat I believe. If you debug your program, your Double will always be displayed scientifically, so what we'll want is to force it into a String. This can be achieved in Java in multiple ways, and you can look it up here:
How to print double value without scientific notation using Java?
In terms of your specific question about Jackson, I've written up some code for you:
public class ObjectMapperTest {
public static void main(String[] args) throws JsonProcessingException {
IntegerSchema schema = new IntegerSchema();
schema.type = "Int";
schema.max = 10200000000d;
schema.min = 100d;
ObjectMapper m = new ObjectMapper();
System.out.println(m.writeValueAsString(schema));
}
public static class IntegerSchema {
#JsonProperty
String type;
#JsonProperty
double min;
#JsonProperty
#JsonSerialize(using=MyDoubleDesirializer.class)
double max;
}
public static class MyDoubleDesirializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
// TODO Auto-generated method stub
BigDecimal d = new BigDecimal(value);
gen.writeNumber(d.toPlainString());
}
}
}
The trick is to register a custom Serializer for your Double value. This way, you can control what you want.
I am using the BigDecimal value to create a String representation of your Double. The output then becomes (for the specific example):
{"type":"Int","min":100.0,"max":10200000000}
I hope that solves your problem.
Artur

Feature.WRITE_BIGDECIMAL_AS_PLAIN
set this for your Object Mapper

I know I am answering late, but something I faced may help other
While converting a BigDecimal I have faced below is working
mapper = mapper.setNodeFactory(JsonNodeFactory.withExactBigDecimals(true));
while this is not working for me
mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);

Update for Jakson 2.9.10:
Property WRITE_BIGDECIMAL_AS_PLAIN replaced to com.fasterxml.jackson.core.JsonGenerator. You could use:
mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);

If you are using ValueToTree then no need of any factory settings. only problem with ValueToTree is it is converting as TextNode (String Fromat), So if you have any logic based on ObjectNodes it will not work

You should use
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
To avoid scientific notation on floating numbers.
You can find an example below.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
String test ="{\"doubleValue\" : 0.00001}";
try {
System.out.println(mapper.readTree(test).toPrettyString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Output
{
"doubleValue" : 0.00001
}

Related

How to get a JsonProcessingException using Jackson

Might be a strange question but indeed I would like to achieve a a bit more coverage on my tests and although I coded against a JsonProcessingException I can't create a payload that generates this exception, maybe because Jackson is quite smart and converts everything to a string, and even for bad strings it goes around the JSON specs. My problem is that Jackson is quite good :)
I basically want a payload that when I run this, it break with JsonProcessingException:
String jsonPayload = objectMapper.writeValueAsString(payload);
I've tried some like:
HashMap<String, String> invalidJSONPayload= new HashMap<>();
invalidJSONPayload.put("021",021);
invalidJSONPayload.put("---",021);
invalidJSONPayload.put("~",021);
I'm not fussed with the type, so feel free to suggest another one. An empty object for example, throws JsonMappingException and I already catch that one as well.
I wanted to do the same thing, and eventually accomplished it by using the Mockito "spy" function, which wraps a real object with a mock object. All calls to the mock object get forwarded to the real object, except those you are trying to mock. For example:
ObjectMapper om = Mockito.spy(new ObjectMapper());
Mockito.when( om.writeValueAsString(ErrorObject.class)).thenThrow(new JsonProcessingException("") {});
All usages of om will be handled by the underlying ObjectMapper instance until an instance of ErrorObject gets passed in, at which point the JsonProcessingException will be thrown.
The newJsonProcessingException is created as an anonymous class, as it is a protected class and only a sub-class can be instantiated.
Building off of Liam's answer, mocking the toString() method with a cycle also causes Jackson to break.
#Test
public void forceJsonParseException() {
try {
Object mockItem = mock(Object.class);
when(mockItem.toString()).thenReturn(mockItem.getClass().getName());
new ObjectMapper().writeValueAsString(mockItem);
fail("did not throw JsonProcessingException");
} catch (JsonProcessingException e) {
//pass
}
}
EDIT: It's way easier than that. A Mockito mock will always throw it. o.o;;
You could use something like this:
private static class ClassThatJacksonCannotSerialize {
private final ClassThatJacksonCannotSerialize self = this;
#Override
public String toString() {
return self.getClass().getName();
}
}
Which results in a JsonProcessingException with message Direct self-reference leading to cycle (through reference chain: ClassThatJacksonCannotSerialize["self"])
following on #Mike.Mathieson answer
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
public class JacksonTest {
#Test(expected = JsonProcessingException.class)
// actually throws an InvalidDefinitionException (which extends JsonProcessingException)
public void encodeThrowsException() throws JsonProcessingException {
new ObjectMapper().writeValueAsString(new Object());
}
}
https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/exc/InvalidDefinitionException.html
note that this test won't work if the ObjectMapper have been configured to disable SerializationFeature.FAIL_ON_EMPTY_BEANS, e.g.
new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.writeValueAsString(new Object());
For me, if a class has no public fields/methods, writeValueAsString will throw a JsonMappingException (no serializer found for class...)
private static class ClassThatJacksonCannotSerialize {}
private void forceProcessingException() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(value);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
Throw exception in getter to simulate JsonProcessingException.
public static class TestData {
public String getEx() throws JsonProcessingException { throw new JsonParseException(null, "test"); }
}
ObjectMapper().writeValueAsString(new TestData());
You can get a JsonProcessingException if mapping two fields to the same property.
class InvalidObject {
#JsonProperty("s")
private String x = "value1";
#JsonProperty("s")
private String y = "value2";
}
Exception message is "Multiple fields representing property "s":..."
Trying to mock using mock(ObjectMapper.class) will invariably result in Checked exception is invalid for this method! as it is not possible to throw checked exception (JsonProcessingException extends IOException). Creating a self referencing value object like other answers suggested could be too convoluted for many cases and looks like a hack.
The easiest way I found is to extend ObjectMapper and then use that in your test method. You should pass the subclass to SUT
#Test
public void buildJsonSwallowsJsonProcessingException() {
class MyObjectMapper extends ObjectMapper {
#Override
public String writeValueAsString(Object value)
throws com.fasterxml.jackson.core.JsonProcessingException {
throw new com.fasterxml.jackson.core.JsonProcessingException("Forced error") {};
}
}
ObjectMapper objectMapper = new MyObjectMapper();
SUTBean sutbean = new SUTBean(objectMapper);
sutbean.testMethod();
assertTrue(expected, actual);
}
i was land on this question because i had the same target of the questioner:
Might be a strange question but indeed I would like to achieve a a bit
more coverage on my tests
IMHO Mockito solution it's more elegant and not introduce misunderstanding but I challenged a colleague to find another solution and he did. This is the solution:
#Test
#DisplayName("Object To Json KO")
void objectToJsonKOTest() {
KafkaMessageDTO o = new KafkaMessageDTO() {
#Override
public String getAuthCode() {
int a = 2/0;
return super.getAuthCode();
}
};
String s = mapper.writeValueAsString(o);
Assertions.assertTrue(s.isEmpty());
}
This is trash, high level trash :) , but it works and I wanted to share it with you as an alternative to mock
ByeBye
I found this in Jackson Github issue; but it solved my problem and I am able to throw the JsonProcessingException.
#Test
public void forceJsonParseException() {
try {
Object mockItem = mock(Object.class);
when(mockItem.toString()).thenReturn(mockItem.getClass().getName());
new ObjectMapper().writeValueAsString(mockItem);
fail("did not throw JsonProcessingException");
} catch (JsonProcessingException e) {
//pass
}
}
Now how I used this in my code
Need to test this method
public String geResponse(MyObject myObject) {
try {
return objectMapper.writeValueAsString(myObject);
} catch (JsonProcessingException e) {
log.error("Service response JsonParsing error {} ", e.getMessage());
return "Validation Service response JsonParsing error {} "+ e.getMessage();
}
}
This how I test the JsonProcessingException
#SneakyThrows
#Test
public void testGetValidationResponseNegative() {
MyObject mockItem = mock(MyObject.class);
when(mockItem.toString()).thenReturn(mockItem.getClass().getName());
String vr = geResponse(mockItem);
assertTrue(!vr.isEmpty());
}
I hope this helps!!!
You can get JsonProcessingException via below code
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class SomePOJOClass
{
// Lets assume there are your fields here.
}
// ...
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode objectNode = new ObjectNode();
objectNode.put("unknown-field-in-the-class", "and-its-value");
SomePOJOClass somePOJOClass = objectMapper.treeToValue(objectNode, SomePOJOClass .class);
Use this value, it shall break, it broke for me::
ObjectMapper objectMapper = new ObjectMapper();
String jsonStringValue ="{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}";
try {
MyCustomClass classObject = objectMapper.readValue(jsonStringValue, MyCustomClass.class);
} catch(JsonProcessingException ex) {
//LOGGER / SYSOUT here
}
ERROR::JsonProcessingException / JsonParseException
Unexpected character (''' (code 39)): was expecting double-quote to start field name
It really worked for me.. cheers..

BigDecimal has scientific notation in soap message

I have got strange problem with our webservice.
I have got object OrderPosition which has got a price (which is xsd:decimal with fractionDigits = 9). Apache CXF generate proxy classes for me, and this field is BigDecimal. When I'd like to send value greater than 10000000.00000, this field in soap message has got scientific notation (for example 1.1423E+7).
How can I enforce that the value has not been sent in scientific notation.
Here is one way this can be done.
BigDecimal has a constructor which takes input number as a string. This when used, preservs the input formatting when its .toString() method is called. e.g.
BigDecimal bd = new BigDecimal("10000000.00000");
System.out.println(bd);
will print 10000000.00000.
This can be leveraged in Jaxb XmlAdapters. Jaxb XmlAdapters offer a convenient way to control/customize the marshalling/unmashalling process. A typical adapter for BigDecimmal would look like as follows.
public class BigDecimalXmlAdapter extends XmlAdapter{
#Override
public String marshal(BigDecimal bigDecimal) throws Exception {
if (bigDecimal != null){
return bigDecimal.toString();
}
else {
return null;
}
}
#Override
public BigDecimal unmarshal(String s) throws Exception {
try {
return new BigDecimal(s);
} catch (NumberFormatException e) {
return null;
}
}
}
This needs to be registered with Jaxb context. Here is the link with complete example.
#Santosh Thank you! XMLAdapter was what I needed.
Furthermore as I said in my question I generate client classes with Apache CXF. In this kind of problem I had to add the following code to bindings.xjb (binding file which is use to cxf-codegen-plugin in maven).
<jaxb:javaType name="java.math.BigDecimal" xmlType="xs:decimal"
parseMethod="sample.BigDecimalFormater.parseBigDecimal"
printMethod="sample.BigDecimalFormater.printBigDecimal" />
This is my formatter code:
public class BigDecimalFormater {
public static String printBigDecimal(BigDecimal value) {
value.setScale(5);
return value.toPlainString();
}
public static BigDecimal parseBigDecimal(String value) {
return new BigDecimal(value);
}
}
Then this plugin generate Adapter for me
public class Adapter1 extends XmlAdapter<String, BigDecimal> {
public BigDecimal unmarshal(String value) {
return (sample.BigDecimalFormater.parseBigDecimal(value));
}
public String marshal(BigDecimal value) {
return (sample.BigDecimalFormater.printBigDecimal(value));
}
}
In generated class BigDecimal field has got annotation #XmlJavaTypeAdapter(Adapter1 .class), and it resolved problem.

Is there a way to read a string into the Jackson API to easily get back a JSON object

Is there was a way to pass a String into some Jackson object and have it populate the JSON obj for me? Maybe I'm comparing apples to oranges but the json-rpc-1.0.jar library allows me to do this:
// string will be read in from file but putting the string below just to show what i'm trying to do.
JSONObject jsonObj;
String testStr = "{"blah":123, "aaa": "got here", "foo":"bar", "bar":123}";
jsonObj = new JSONObject(testStr);
jsonObj.put("blah",345);
If I execute
System.out.println(jsonObj);
I get:
{"blah":345, "aaa": "got here", "foo":"bar", "bar":123}
The problem with the json-rpc-1.0.jar file is it doesn't play nicely with long primitive types. For some reason, it converts long data to something like 1.32e9 if I tried to assign a timestamp (long data type) to a field.
I found Jackson (jackson-core-2.2.3.jar) is nicer to longs, preserving the 10-13 digits I need for my timestamp. However, I can't find anything that works like the above snippet of code in Jackson. The closest might be ObjectMapper.readValue but it's not exactly like above.
Please let me know if this is possible or if I'm just dreaming. Thanks in advance for your help. In the meantime, I will try to look at the API some more.
IMO this is not how Jackson is meant to be used. With Jackson, an object should be serialized with the fields of its class. You shouldn't be adding anything to that JSON afterwards. For the sake of the question, however, here's what you can do. Take for example
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
MyClass a = new MyClass();
ObjectNode node = mapper.<ObjectNode>valueToTree(a);
node.put("blah", "123");
System.out.println(node);
}
static class MyClass {
private String value = "some text";
private long timestamp = System.currentTimeMillis();
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
which prints
{"value":"some text","timestamp":1384233053765,"blah":"123"}
The valueToTree() method will convert your object into an ObjectNode which is kind of a tree that holds the various JSON elements. You can modify this ObjectNode by adding or removing elements. That is what we do with node.put("blah", "123");. It will add a Json object with name blah and value "123".

Serialize a Double to 2 decimal places using Jackson

I'm using Jackson, with Spring MVC, to write out some simple objects as JSON. One of the objects, has an amount property, of type Double. (I know that Double should not be used as a monetary amount. However, this is not my code.)
In the JSON output, I'd like to restrict the amount to 2 decimal places. Currently it is shown as:
"amount":459.99999999999994
I've tried using Spring 3's #NumberFormat annotation, but haven't had success in that direction. Looks like others had issues too: MappingJacksonHttpMessageConverter's ObjectMapper does not use ConversionService when binding JSON to JavaBean propertiesenter link description here.
Also, I tried using the #JsonSerialize annotation, with a custom serializer.
In the model:
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
And serializer implementation:
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
//write the word 'null' if there's no value available
jgen.writeNull();
} else {
final String pattern = ".##";
//final String pattern = "###,###,##0.00";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}
The CustomDoubleSerializer "appears" to work. However, can anyone suggest any other simpler (or more standard) way of doing this.
I know that Double should not be used as a monetary amount. However,
this is not my code.
Indeed, it should not. BigDecimal is a much better choice for storing monetary amounts because it is lossless and provides more control of the decimal places.
So for people who do have control over the code, it can be used like this:
double amount = 111.222;
setAmount(new BigDecimal(amount).setScale(2, BigDecimal.ROUND_HALF_UP));
That will serialize as 111.22. No custom serializers needed.
I had a similar situation in my project. I had added the formatting code to the setter method of the POJO. DecimalFormatter, Math and other classes ended up rounding off the value, however, my requirement was not to round off the value but only to limit display to 2 decimal places.
I recreated this scenario.
Product is a POJO which has a member Double amount.
JavaToJSON is a class that will create an instance of Product and convert it to JSON.
The setter setAmount in Product will take care of formatting to 2 decimal places.
Here is the complete code.
Product.java
package com;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Product {
private String name;
private Double amount;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
BigDecimal bd = new BigDecimal(amount).setScale(2, RoundingMode.FLOOR);
this.amount = bd.doubleValue();
}
#Override
public String toString() {
return "Product [name=" + name + ", amount=" + amount + "]";
}
}
JavaToJSON.java
package com;
import java.io.File;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
public class JavaToJSON {
public static void main(String[] args){
ObjectMapper mapper = new ObjectMapper();
try {
Product product = new Product();
product.setName("TestProduct");
product.setAmount(Double.valueOf("459.99999999999994"));
// Convert product to JSON and write to file
mapper.writeValue(new File("d:\\user.json"), product);
// display to console
System.out.println(product);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I haven't accumulated enough points so I am not able to upload the screenshots to show you the output.
Hope this helps.
Regarding what was stated above, I just wanted to fix a little something, so that people won't waste time on it as I did. One should actually use
BigDecimal.valueOf(amount).xxx
instead of
new BigDecimal(amount).xxx
and this is actually somehow critical. Because if you don't, your decimal amount will be messed up. This is a limitation of floating point representation, as stated here.
Best way I have seen till now is to create a customized serializer and #JsonSerializer(using=NewClass.class). Wanted to try with #JsonFormat(pattern=".##") or so, but it may not work according one comment of OP(I think the formatter does not honor that)
See here: https://github.com/FasterXML/jackson-databind/issues/632
public class MoneyDeserializer extends JsonDeserializer<BigDecimal> {
private NumberDeserializers.BigDecimalDeserializer delegate = NumberDeserializers.BigDecimalDeserializer.instance;
#Override
public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
BigDecimal bd = delegate.deserialize(jp, ctxt);
bd = bd.setScale(2, RoundingMode.HALF_UP);
return bd;
}
}
BUT, although more convenient and less code is written, usually, deciding the scale of a field is concern of business logic, not part of (de)serialization. Be clear about that. Jackson should be able to just pass the data as-is.
Note that 459.99999999999994 is effectively 460 and is expected to be serialized in this way. So, your logic should be trickier than just dropping digits.
I might suggest something like:
Math.round(value*10)/10.0
You might want to put it into setter, and get rid of custom serialization.

Jackson - converting java object to json - Need all key keys to upper case

Need your help on conversion of java objects to json.
current the json result showing all the key in small letter case, i need it to be upper case.
ObjectMapper mapper = new ObjectMapper();
Writer strWriter = new StringWriter();
mapper.writeValue(strWriter, obj);
String jsonString= strWriter.toString();
and the result is
[{"flags":"1","name":"Peter","location":"London","startDate":"2012-01-06 00:00"}]
but i want results like this (all key key value should be in UPPER CASE):
[{"FLAGS":"YU","NAME":"Peter","LOCATION":"London","STARTDATE":"2012-01-06 00:00"}]
and also is it possible to get like this also (key first letter in upper case):
[{"Flags":"1","Name":"Peter","Location":"London","StartDate":"2012-01-06 00:00"}]
Can anyone help me on this.
Thanks in advance.
There are multiple ways to do it with Jackson.
Annotations
You could annotate your object with #JsonProperty annotations on your fields or on your getter methods.
Example:
#JsonProperty("Name")
public final String name;
#JsonProperty("Location")
public String getLocation() {
return location;
}
Implement JsonSerializableWithType interface
#Override
public void serialize(final JsonGenerator jG, final SerializerProvider p)
throws IOException, JsonProcessingException
{
serializeWithType(jG, p, null);
}
#Override
public void serializeWithType(final JsonGenerator jG, final SerializerProvider p, final TypeSerializer typeSer)
throws IOException, JsonProcessingException
{
// here you can do your own serialization
}
I would advice to use the #JsonNaming annotation on class level.
Yet afaik there is no strategy out there to fit your needs for total uppercase. But you can probably just write your own.
In my case I needed first character uppercase. So I used the existing
#JsonNaming(value = UpperCamelCaseStrategy.class).

Categories