ObjectWriter print json double with precision [duplicate] - java

Suppose I have an object with
private Double test;
// Need specific output in JSON via Jackson: test = 24.6000
When output to JSON via Jackson, I get 24.6, but I need the exact 4-decimal output as in the example. Does Jackson allow this?
For example, for Dates, we found a way to force MM/dd/yyyy:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM/dd/yyyy")
Date myDate;
We need something similar for Decimal formatting.

One way of doing this is to use custom json serializer and specify in #JsonSerialize.
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
jgen.writeNull();
} else {
final String pattern = ".####";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}

You can try to use com.fasterxml.jackson.databind.util.RawValue:
BigDecimal d = new BigDecimal(new BigInteger("246000"), 4);
RawValue rv = new RawValue(d.toPlainString());
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode output = objectMapper.createObjectNode();
output.putRawValue("decimal_value", rv);
System.out.println(output.toPrettyString());
//output is:
//{
// "decimal_value" : 24.6000
//}

Related

Deserialize multiple fields to one by Jackson

I have following json
{"val": 501, "scale": 2}
Field scale represent how much is decimal point shifted in value (filed val). In this case there are to places, therefore result is value 5.01.
I would like to map it to following class
public class ValueClass {
#JsonProperty("val")
#JsonDeserialize(using = ValueDeserializer.class)
private BigDecimal value;
}
I would like to use custom deserializer for this however it is not clear to me how to access the other fields of JSON from within the deserializer then the annotated one.
#SuppressWarnings("serial")
class ValueDeserializer extends StdDeserializer<BigDecimal> {
protected ValueDeserializer() {
super(BigDecimal.class);
}
#Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
var val = p.readValueAs(Integer.class);
int scale = ??; // <-- How to access "scale" field here?
return new BigDecimal(val).scaleByPowerOfTen(-scale);
}
}
P.S. I know that I could you #JsonCreator in this simple case.
public class ValueClass {
private BigDecimal value;
#JsonCreator
public ValueClass(//
#JsonProperty("val") Integer val, //
#JsonProperty("scale") Integer scale //
) {
this.value = new BigDecimal(val).scaleByPowerOfTen(-scale);
}
}
Nevertheless the real use case is much more complex and it would be more beneficial to keep the the logic inside deserializer (if possible) for easier reuse.
Thanks for help.
Edit 1
As a replay to Chaosfire here is a a bit more clarification to my case.
More real JSON which I need to parse looks this
{"val1":501, "scale":2, "val2":407, "val3":86}
Value of scale filed is shared as divider for multiple fields.
The JSON object has about 10 fields like above and 50 other fields which are relatively straightforward. The reason why I would prefer the deserializer is to avoid huge #JsonCreator which would mainly repeat input values.
This is not possible with your current setup, you provide to the deserializer only the val node, but you need the entire object to access scale node.
Since using #JsonCreator is undesirable, you could change the deserializer to handle ValueClass:
public class ValueDeserializer extends StdDeserializer<ValueClass> {
public ValueDeserializer() {
super(ValueClass.class);
}
#Override
public ValueClass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
int scale = node.get("scale").asInt();
ValueClass valueClass = new ValueClass();
JavaType javaType = context.getTypeFactory().constructType(ValueClass.class);
// Introspect the given type
BeanDescription beanDescription = context.getConfig().introspect(javaType);
// Find properties
List<BeanPropertyDefinition> properties = beanDescription.findProperties();
for (BeanPropertyDefinition property : properties) {
String propertyName = property.getName();//get name as in json
String propertyValue = node.get(propertyName).asText();
BigDecimal decimal = new BigDecimal(propertyValue).scaleByPowerOfTen(-scale);
AnnotatedMember accessor = property.getMutator();
accessor.setValue(valueClass, decimal);
}
return valueClass;
}
}
To avoid manually writing property names and setting their values, properties are introspected from java type. This approach is heavily inspired by this answer, you can check it for additional info and possible pitfalls. I believe setting the rest of the fields should be straightforward, using this as a basis.
And simple test:
#JsonDeserialize(using = ValueDeserializer.class)
public class ValueClass {
#JsonProperty("val1")
private BigDecimal value1;
private BigDecimal val2;
private BigDecimal val3;
//setters and getters
#Override
public String toString() {
return "ValueClass{" +
"value1=" + value1 +
", val2=" + val2 +
", val3=" + val3 +
'}';
}
}
Main:
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"val1\":501, \"scale\":2, \"val2\":407, \"val3\":86}";
ObjectMapper mapper = new ObjectMapper();
ValueClass value = mapper.readValue(json, ValueClass.class);
System.out.println(value);
}
}
Prints - ValueClass{value1=5.01, val2=4.07, val3=0.86}.

Convert enum to JSON String with Jackson

Im trying to convert an Enum class into a JSON string using jackson, the problem is the class is in a jar file so I am looking for better soultion then changing it.
when I use this code I get the following output:
Code
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
BrainWave brainwave = BrainWave.DELTA;
brainwave.value(50);
System.out.println(ow.writeValueAsString(brainwave));
Output
"DELTA"
The output I want:
{
"type" : 1,
"value" : 50
}
I know i can use #JsonFormat but As I stated before, I rather not change the jar file.
Try a StdDeserializer - this article on Enum Serialization shows the different ways but in your case you'll want something like this (this is a rewrite of their example at the bottom based on your snippet above)
public class DeltaEnumDeserializer extends StdDeserializer<DELTA> {
#Override
public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final int type = node.get("type").asInt();
final int value = node.get("value").asInt();
for (final DELTA curr : DELTA.values()) {
if (curr.type == type && curr.value == value) {
return curr;
}
}
return null;
}
}
This article shows a snippet of code on linking up the deserializer. ie.
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addDeserializer(DELTA.class, new DeltaEnumDeserializer());
mapper.registerModule(module);
DELTA readValue = mapper.readValue(json, DELTA.class);

JsonNode comparison excluding fields

I'm trying to deserialise a JSON string using ObjectMapper (Jackson) and exclude a field while performing the deserialisation.
My code is as follows:
String aContent = new String(Files.readAllBytes(Paths.get(aFile)));
String bContent = new String(Files.readAllBytes(Paths.get(bFile)));
ObjectMapper mapper = new ObjectMapper();
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("_idFilter", SimpleBeanPropertyFilter.serializeAllExcept("_id"));
mapper.setFilterProvider(filterProvider);
JsonNode tree1 = mapper.readTree(aContent);
JsonNode tree2 = mapper.readTree(bContent);
String x = mapper.writeValueAsString(tree1);
return tree1.equals(tree2);
Both x and tree1 and tree2 contains the value _id in the JSON String but it isn't removed.
You are following Ignore Fields Using Filters except the first step
First, we need to define the filter on the java object:
#JsonFilter("myFilter")
public class MyDtoWithFilter { ... }
So currently you supposed to add
#JsonFilter("_idFilter")
public class JsonNode {
It's not possible so you need to create a class that extends JsonNode and use it instead
#JsonFilter("_idFilter")
public class MyJsonNode extends JsonNode {
If you don't want to implement all abstract method define as abstract
#JsonFilter("_idFilter")
public abstract class MyJsonNode extends JsonNode {
}
And in your code:
MyJsonNode tree1 = (MyJsonNode) mapper.readTree(aContent);
MyJsonNode tree2 = (MyJsonNode) mapper.readTree(bContent);
FilterProvider are meant to be used with custom Object like here
If you want to stick with JsonNode, use this method :
String aContent = new String("{\"a\":1,\"b\":2,\"_id\":\"id\"}");
ObjectMapper mapper = new ObjectMapper();
JsonNode tree1 = mapper.readTree(aContent);
ObjectNode object = (ObjectNode) tree1;
object.remove(Arrays.asList("_id"));
System.out.println(mapper.writeValueAsString(object));
Will print :
{"a":1,"b":2}
If you use Jackson 2.6 or higher, you can use a FilteringParserDelegate with a custom TokenFilter.
public class PropertyBasedIgnoreFilter extends TokenFilter {
protected Set<String> ignoreProperties;
public PropertyBasedIgnoreFilter(String... properties) {
ignoreProperties = new HashSet<>(Arrays.asList(properties));
}
#Override
public TokenFilter includeProperty(String name) {
if (ignoreProperties.contains(name)) {
return null;
}
return INCLUDE_ALL;
}
}
When creating the FilteringParserDelegate with this PropertyBasedIgnoreFilter, make sure to set the booleans includePath and allowMultipleMatches both to true.
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonInput = "{\"_p1\":1,\"_p2\":2,\"_id\":\"id\",\"_p3\":{\"_p3.1\":3.1,\"_id\":\"id\"}}";
JsonParser filteredParser = new FilteringParserDelegate(mapper.getFactory().createParser(new ByteArrayInputStream(jsonInput.getBytes())),
new PropertyBasedIgnoreFilter("_id"),
true,
true);
JsonNode tree = mapper.readTree(filteredParser);
System.out.println(jsonInput);
System.out.println(tree);
System.out.println(jsonInput.equals(tree.toString()));
}
}
prints
{"_p1":1,"_p2":2,"_id":"id","_p3":{"_p3.1":3.1,"_id":"id"}}
{"_p1":1,"_p2":2,"_p3":{"_p3.1":3.1,"_id":"id"}}
false
As you can see, nested occurrences of _idare not filtered out. In case that's not what you need, you can of course extend my PropertyBasedIgnoreFilter with your own TokenFilter implementation.

How to deserialize some JSON parameters as one new variable in DTO

How to deserialize JSON (using Jackson) into Java object if some JSON parameters should be used to create variable of Java DTO(but not be deserialized as dto variables).
For example I have JSON {"sideA" : 2, "sideB" : 4, "useless_parameter" : "useless_information"} and I need to get result of toString (of deserialized Java object) something like : RectangleDto{area = 8, useless_parameter = "useless_information"}
If I need to deserialize "useless_parameter" then I can use #JsonGetter("useless_information"), so what should I do with "sideA" and "sideB" if I need to take area as variable of RectangleDto? I already have a method for conversion JSON parameters into this variable.
Probably you need a custom deserializer:
class MyDeserializer extends StdDeserializer<RectangleDto> {
public MyDeserializer() {
this(null);
}
protected MyDeserializer(Class<?> vc) {
super(vc);
}
#Override
public RectangleDto deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int a = node.get("sideA").intValue();
int b = node.get("sideB").intValue();
String useless_parameter = node.get("useless_parameter").asText();
RectangleDto test = new RectangleDto();
test.setArea(a * b);
test.setUseless_parameter(useless_parameter);
return test;
}
}
Then register the deserializer on the class
#JsonDeserialize(using = MyDeserializer.class)
public class RectangleDto {
private int area;
private String useless_parameter;
// getters, setters...
}
Then this will work as you want:
RectangleDto r = mapper.readValue("{\"sideA\" : 2, \"sideB\" : 4, \"useless_parameter\" : \"useless_information\"}", RectangleDto.class);
System.out.println(r);
results in
RectangleDto{area=8, useless_parameter='useless_information'}
You could add #JsonProperty for the area and also use #JsonIgnore for sideA and SideB. Please check
Jackson :: adding extra fields to an object in serialization

Overriding default behavior for Jackson serializers

I am using Jackson to prepare a JSON object to be inserted into ElasticSearch (ES is somewhat unrelated here). The object looks like:
class TimestampedCount {
private Date timestamp;
private Map<Long, Integer> counts;
}
The default behavior is, as expected, to convert the counts variable to an object. However, due to how I am storing the data in ES, I'd like to coerce the map to a byte[] or String field, without having to change the defined type. In other words, I want it stored differently from how it's being used. For example, if I convert it to a String, I'd expect something like the following in the final JSON:
{
"timestamp": 12355812312,
"counts": "{1: 15431, 2: 15423, 3: 1314}"
}
Is there a way to do this without having to write a custom serializer/deserializer?
You can simply add a 'getter' method which would convert the Map into suitable format. Here is an example returning a byte array:
public class JacksonGetter {
static class TimestampedCount {
private final Date timestamp;
private final Map<Long, Integer> counts;
public TimestampedCount(final Date timestamp, final Map<Long, Integer> counts) {
this.timestamp = timestamp;
this.counts = counts;
}
public Date getTimestamp() { return timestamp; }
#JsonProperty("counts")
public byte[] getCountsAsBytes() {
return counts.toString().getBytes();
}
}
public static void main(String[] args) throws JsonProcessingException {
final TimestampedCount timestampedCount = new TimestampedCount(
new Date(),
Collections.singletonMap(1L, 123));
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(timestampedCount));
}
}
Output:
{"timestamp":1450555085608,"counts":"ezE9MTIzfQ=="}

Categories