Question: The Jackson ObjectMapper deserializer is converting a null value to a 0 for a Double field. I need it to either be deserialized to null or Double.NaN. How can I do this?
Do I need to write a custom Double deserializer that maps null to Double.NaN?
Already tried: I have scoured the DeserializationFeature Enum but I don't think anything applies. (http://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_NULL_FOR_PRIMITIVES)
Motivation: I am deserializing a json object into a custom object (Thing) with code similar to the following. I need the deserializer to keep the value as null or change it to Double.NaN because I need to be able to differential between the 0 case (located at latitude/longitude/altitude = 0) and the null/Double.NaN case (when these values are unavailable).
Jackson deserializing
try {
ObjectMapper mapper = new ObjectMapper();
Thing t = mapper.readValue(new File("foobar/json.txt"), Thing.class);
} catch (JsonParseException e) {
...do stuff..
}
Contents of json.txt. Note that the value null is actually written in the file. It is not left empty. It is not the empty string. It is actuall the word null.
{
"thing" : {
"longitude" : null,
"latitude" : null,
"altitude" : null
}
}
Code for Thing
import java.io.Serializable;
public class Thing implements Serializable {
private static final long serialVersionUID = 1L;
Double latitude;
Double longitude;
Double altitude;
public Thing(Double latitude, Double longitude, Double altitude) {
this.latitude = latitude;
this.longitude = longitude;
this.altitude = altitude;
}
...rest of code...
}
This is what I did:
public class DoubleDeserializer extends JsonDeserializer<Double> {
#Override
public Double deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String doubleStr = parser.getText();
if (doubleStr.isEmpty() || doubleStr == null) {
return null;
}
return new Double(doubleStr);
}
}
and then in my bean:
#JsonDeserialize(using = DoubleDeserializer.class)
private Double partialPressureCO2;
Hope this helps.
The solution that worked for me was to make a custom JSON Deserializer that transformed null into Double.NaN. Adapting what I wrote to match my example code above it would look like this.
public class ThingDeserializer extends JsonDeserializer<Thing> {
#Override
public Thing deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
Thing thingy = new Thing();
JsonNode node = jp.getCodec().readTree(jp);
if (node.get("latitude").isNull()) {
thingy.setLatitude(Double.NaN);
} else {
thingy.setLatitude(node.get("latitude").asDouble());
}
if (node.get("longitude").isNull()) {
thingy.setLongitude(Double.NaN);
} else {
thingy.setLongitude(node.get("longitude").asDouble());
}
if (node.get("altitude").isNull()) {
thingy.setAltitude(Double.NaN);
} else {
thingy.setLatitude(node.get("altitude").asDouble());
}
return thingy;
}
then I registered the deserializer in the class Thing by adding the annotation above the class declaration.
#JsonDeserialize(using = ThingDeserializer.class)
public class Thing implements Serializable {
... class code here ...
}
Note I think a better answer would be to deserialize the Double class rather than the Thing class. By deserializing the Double you could generalize the conversion from null to NaN. This would also do away with pulling the specific fields from the node by field name. I could not figure out how to do it on a limited time budget so this is what worked for me. Also, the deserialization is actually being implicitly called by Jackson through a REST api so I am not sure how/if this changes things. I would love to see a solution that would accomplish this though.
Related
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}.
I am stuck with a problem which I have been trying to solve since last two days. I have an Integer object and a Float object which I do not want to display in the JSON response if it is 0. I am trying to achieve this with #JsonInclude(value=Include.NON_NULL) but it doesn’t seem to be working.
Does anyone have any suggestion and can explain me what I am doing wrong here?
Lets say the model class is something like this:
#JsonInclude(value = Include.NON_NULL)
public class myClassInfo {
String originalQuery;
String normalizedQuery;
Long id;
Integer performanceStatus;
Float atcPercentage;
Integer ruleOn;
Integer ruleOff;
}
I have the getter and setter methods accordingly. I want to display the atcPercentage, ruleOn and ruleOff only if it is not 0. How would I do that? I hope this explanation helps in understanding my problem. I have tried NON_NULL and it doesn't seems to be working. My understanding if I define the JsonInclude in the beginning of the class, that should be applicable to all the fields. Correct me if I am wrong.
You can write your own filter and use it as below:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonApp {
public static void main(String[] args) throws Exception {
ClassInfo classInfo = new ClassInfo();
classInfo.setId(0L);
classInfo.setAtcPercentage(0F);
classInfo.setPerformanceStatus(0);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(classInfo));
}
}
#JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = ZeroNumberFilter.class)
class ClassInfo {
private Long id;
private Integer performanceStatus;
private Float atcPercentage;
// getters, setters
}
class ZeroNumberFilter {
#Override
public boolean equals(final Object obj) {
if (obj instanceof Number) {
final Number number = (Number) obj;
return Double.compare(number.doubleValue(), 0) == 0;
}
return false;
}
}
prints {} - empty object. When we change all values to 1, it prints:
{"id":1,"performanceStatus":1,"atcPercentage":1.0}
Include.NON_NULL filters only properties with null value. You could use Include.NON_DEFAULT but in this case you should change your POJO and declare default values for all fields:
#JsonInclude(value = JsonInclude.Include.NON_DEFAULT)
class ClassInfo {
private Long id = 0L;
private Integer performanceStatus = 0;
private Float atcPercentage = 0F;
// getters, setters
}
But this solution could have some drawbacks if your business logic depends on null values somewhere.
See also:
Serialize Only Fields that meet a Custom Criteria with Jackson
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
I am trying to create an #JsonDeserializer that will work across classes. I am using JAX-RS and the incoming json string will have fields in snake case. I want to override the json deserialization so that my java objects do not have snake-case fields. Since the creation of the java object is happening within JAX-RS, I am using the #JsonDeserializer annotation on all my request classes. My current implementation has a generic base class, but I need to extend it for all the concrete classes so that I can pass in the actual class I want to create. Is there any way to do this more generically?
For example, I have multiple request objects like this:
#JsonDeserialize(using = MyRequestDeserializer.class)
public class MyRequest {
....
}
I have created a generic deserializer like so:
public class GenericRequestDeserializer extends JsonDeserializer<Object> {
private static ObjectMapper objectMapper = new ObjectMapper();
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return null;
}
protected Object deserializeIt(JsonParser jsonParser, Class cls) {
try {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Iterator<String> fieldNames = node.fieldNames();
Object object = cls.newInstance();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode value = node.get(fieldName);
String newFieldName = convertFieldName(fieldName);
//TODO: currently failing if I do not find a field, should the exception be swallowed?
Class returnType = object.getClass().getMethod("get" + newFieldName).getReturnType();
Method setMethod = object.getClass().getMethod("set" + newFieldName, returnType);
Object valueToSet = null;
if(value.isTextual()) {
valueToSet = value.asText();
} else if(value.isContainerNode()) {
valueToSet = objectMapper.readValue(value.toString(), returnType);
} else if (value.isInt()) {
valueToSet = value.asInt();
}
setMethod.invoke(object, valueToSet);
}
return object;
} catch (Exception e) {
throw new TokenizationException(GatewayConstants.STATUS_SYSTEM_ERROR,
"Error in deserializeIt for " + cls.getSimpleName() + " caused by " + e.getMessage(), e);
}
}
private String convertFieldName(String fieldName) {
StringBuilder newFieldName = new StringBuilder();
int length = fieldName.length();
boolean capitalize = true; //first character should be capitalized
for(int i = 0; i < length; i++) {
char current = fieldName.charAt(i);
if(current == '_') {
//remove the underscore and capitalize the next character in line
capitalize = true;
} else if(capitalize) {
newFieldName.append(Character.toUpperCase(current));
capitalize = false;
} else {
newFieldName.append(current);
}
}
return newFieldName.toString();
}
}
But I still need to create a new class per Request in order to pass in the proper class to create:
public class MyRequestDeserializer extends GenericRequestDeserializer {
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return deserializeIt(jsonParser, MyRequest.class);
}
}
Is there any way to get rid of all the MyRequestDeserializer classes? In other words, can the GenericRequestDeserializer figure out what class it is actually deserializing?
So I found a much better option for changing all my objects to snake case. Instead of using Serializers and Deserializers on each class, I was able to inject an ObjectMapper into the JsonProvider in Spring. ObjectMapper already supports a property that will do the camel-case to snake-case automagically. I just needed to overwrite the getSingletons method in my class that extends Application like so:
public class MyApp extends Application {
....
#Override
public Set<Object> getSingletons() {
final Set<Object> objects = new LinkedHashSet<Object>();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objects.add(new JacksonJsonProvider(objectMapper));
return objects;
}
}
Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)