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}.
Related
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 have a class from an SDK that I don't have access to change, but that I would like to serialize a JSON-valid string into.
However, an external API sometimes puts in the wrong type for a Date field.
Long story short: Can I just ignore errors in GSON, or tell Gson to ignore errors on fields, and just get the partial object?
For example, the field should be a double, but I get a Date(number) instead. But I'm not using it anyway, so I don't care, and I don't need the whole process to fail. I just want the parcable fields out, with the faulty fields left null.
NOTE: Writing a deserializer that creates the object I want to have created by Gson defeats the very purpose I propose.
This i a line of code that fails, because a single field is wrong:
Customer customer = gson.fromJson(settings.getCustomerObjectJSONString(), Customer.class);
I would like for it to just skip the field that it cannot parse, because I don't have access to the Customer class, as it is from a generated SDK/library.
I'm aware of two options.
You can use a JSON deserializer implementation to parse JSON elements on your own. However the following example would affect ALL double and Double fields for whatever DTOs passed to that single gson instance, and such a behavior can be deseriable. Unfortunately, I don't know if it's possible to use JsonDeserializer in a "context" way: e.g. let it work for all double and Double fields if those are fields of a certain parent class.
private static final class Dto {
private double primitive;
private Double nullable;
private String string;
}
private static final class FailSafeDoubleJsonDeserializer
implements JsonDeserializer<Double> {
#Override
public Double deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( !element.isJsonPrimitive() ) {
return null;
}
try {
final JsonPrimitive primitive = (JsonPrimitive) element;
final Number number = primitive.getAsNumber();
return number.doubleValue();
} catch ( final NumberFormatException ignored ) {
return null;
}
}
}
private static final JsonDeserializer<Double> failSafeDoubleJsonDeserializer = new FailSafeDoubleJsonDeserializer();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(double.class, failSafeDoubleJsonDeserializer)
.registerTypeAdapter(Double.class, failSafeDoubleJsonDeserializer)
.create();
public static void main(final String... args) {
dump(gson.fromJson("{\"primitive\": 23, \"nullable\": 42, \"string\": \"foo bar\"}", Dto.class));
dump(gson.fromJson("{\"primitive\": \"whatever\", \"nullable\": \"whatever\", \"string\": \"foo bar\"}", Dto.class));
}
private static void dump(final Dto dto) {
out.println(dto.primitive + " " + dto.nullable + " " + dto.string);
}
Another more low level option can be a type adapter implementation. One advantage of this one over the previous example is that you can annotate failing fields with the #JsonAdapter annotation in DTO classes that are known to be potentially broken.
private static final class Dto {
#JsonAdapter(FailSafeDoubleTypeAdapter.class)
private double primitive;
#JsonAdapter(FailSafeDoubleTypeAdapter.class)
private Double nullable;
private String string;
}
private static final class FailSafeDoubleTypeAdapter
extends TypeAdapter<Double> {
#Override
public void write(final JsonWriter writer, final Double value) {
throw new UnsupportedOperationException();
}
#Override
public Double read(final JsonReader reader)
throws IOException {
final JsonToken peek = reader.peek();
if ( peek != NUMBER ) {
reader.skipValue();
return null;
}
return reader.nextDouble();
}
}
private static final Gson gson = new Gson();
public static void main(final String... args) {
dump(gson.fromJson("{\"primitive\": 23, \"nullable\": 42, \"string\": \"foo bar\"}", Dto.class));
dump(gson.fromJson("{\"primitive\": \"whatever\", \"nullable\": {\"subValue\": \"whatever\"}, \"string\": \"foo bar\"}", Dto.class));
}
private static void dump(final Dto dto) {
out.println(dto.primitive + " " + dto.nullable + " " + dto.string);
}
Thus, both examples generate the following output:
23.0 42.0 foo bar
0.0 null foo bar
for
{"primitive": 23, "nullable": 42, "string": "foo bar"}
and {"primitive": "whatever", "nullable": {"subValue": "whatever"}, "string": "foo bar"}
payloads respectively.
I looked at the problem in another perspective i.e. the main requirement mentioned in the OP is
1) You don't care what value present in a particular field
2) You are not going to use the particular field value, and don't want the deserializer to fail because of invalid data
In the above case, you can mark the particular field as TRANSIENT or STATIC. By default, Gson will exclude all fields marked transient or static.
Example:-
private transient Date joiningDate;
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.
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 :)