Using JAX-RS (GlassFish 4) and Jackson as serializer, I encountered a problem during deserialization of POJO properties of type Double (same with Integer).
Lets say I have simple POJO (excluding bunch of other fieds, getters and setters):
public class MapConstraints {
private Double zoomLatitude;
private Double zoomLongitude;
}
When a user send request to API in format { "zoomLatitude": "14.45", "zoomLongitude": ""}, value of zoomLatitude is set to 14.45, but value of zoomLongitude is set to 0.
I expect value of zoomLongitude to be null if no value is presented. I tried configure ObjectMapper with JsonInclude.Include.NON_EMPTY but without success.
Same results with genson.
You can annotate your POJOs with #JsonInclude to indicate which values will be (de)serialized:
#JsonInclude(Include.NON_EMPTY)
private Double zoomLongitude
In Jackson 2.6, the following values are available:
ALWAYS
NON_ABSENT
NON_DEFAULT
NON_EMPTY
NON_NULL
USE_DEFAULTS
However, if #JsonInclude values don't fit your needs, you can create a custom JsonDeserializer:
public class CustomDeserializer extends JsonDeserializer<Double> {
#Override
public Double deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = jp.getText();
if (text == null || text.isEmpty()) {
return null;
} else {
return Double.valueOf(text);
}
}
}
And then just annotate your fields with #JsonDeserialize indicating the deserializer you want to use:
public class MapConstraints {
#JsonDeserialize(using = CustomDeserializer.class)
private Double zoomLatitude;
#JsonDeserialize(using = CustomDeserializer.class)
private Double zoomLongitude;
}
Use this annotation on the POJO:
#JsonInclude(Include.ALWAYS)
Related
I need to convert certain values in json body of all my http requests.
Specifically, the client will send string "null" instead of null reference for String properties and 0 for int properties.
example json body of request
{
"name": "null",
"age": "null"
}
needs to be deserialized into
public class Person {
public String name; // name == null
public int age; // age == 0
}
Initially I added custom String Deserializer to ObjectMapper:
#Bean
public Module convertNullValueToNullReference() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<>(String.class) {
#Override
public String deserialize(JsonParser p, DeserializationContext context) throws IOException {
String value = StringDeserializer.instance.deserialize(p, context);
if ("null".equalsIgnoreCase(value)) {
return null;
}
return value;
}
});
return module;
}
but this obviously does not work for the case of int fields. I need to add type converter to ObjectMapper but there is no addConverter() method in SimpleModule.
I know about #JsonDeserialize(converter = ...) but I need to register the converter globally for all classes.
I searched SO and could only find this related question but the answer is tailored to the specific conversion of dates and is not suitable for my case
#AllArgsConstructor
#Getter
public enum MemberType {
INTERN("name_intern", 1),
EMPLOYEE("name_employee", 10);
private String name;
private int workingMonth;
}
Here is my enum. I want to convert Enum class to JSON string with some constraint.
I want to MemberType has no dependency with Jackson
I want to convert MemberType.INTERN to {id:INTERN, name:"name_intern", workingMonth:10}.
I have lots of Enums want to convert like above. And Their number of property is different each other.
I want resolve this problem through just one global configuration.
I don't want to use explicit java reflection.
Is there a solution that meets the above constraints?
You can use #JsonFormat annotation like this:
#JsonFormat(shape=JsonFormat.Shape.OBJECT)
public enum MemberType { ... }
or you can use #JsonValue annotation like this:
public enum MemberType {
[...]
#JsonValue
public String getName() {
return name;
}
}
or maybe a CustomSerializer for Enum, you can find more details here.
If you implement JsonSerializer,you can custom serialization.
An example is shown below.
#JsonComponent
public final class MediaTypeJsonComponent {
public static class Serializer extends JsonSerializer<MemberType> {
#Override
public void serialize(MemberType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("id", value.name());
gen.writeNumberField("workingMonth", value.getWorkingMonth());
gen.writeStringField("name", value.getName());
gen.writeEndObject();
}
}
//
// If you need,write code.
//public static class Deserializer extends JsonDeserializer<Customer> {
//}
}
Another way is to implement JsonSerialize.
If you want more information, you should refer to:
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html
https://www.baeldung.com/jackson-custom-serialization
How do I use a custom Serializer with Jackson?
https://www.baeldung.com/jackson-serialize-enums
I'm using Jackson to deserialize some JSON and I've run into some trouble while trying to use a custom deserializer for one of the fields.
class MyClass
{
private static class SpecialPropertyDeserializer extends JsonDeserializer<SpecialProperty>
{
#Override
public SpecialProperty deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException
{
// do some custom deserialisation
}
}
private static class SpecialProperty
{
private String m_foo;
private String m_bar;
#JsonCreator
SpecialProperty(#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar)
{
m_foo = foo;
m_bar = bar;
}
}
private String m_identifier;
private String m_version;
#JsonDeserialize(using = SpecialPropertyDeseializer.class)
private SpecialProperty m_specialProperty;
#JsonCreator
MyClass(#JsonProperty("identifier") String identifier,
#JsonProperty("version") String version,
#JsonProperty("specialProperty") SpecialProperty specialProperty)
{
m_identifier = identifier;
m_version = version;
m_specialProperty = specialProperty;
}
}
and this is the JSON I want to deserialize:
{
"identifier" : "some-id",
"version" : "1.7",
"specialProperty" : {
"foo" : "str1",
"bar" : "str2"
},
}
I invoke the mapper as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
return objectMapper.readValue(input, MyClass.class);
I've observed the following behaviour:
Without a special property it all works fine - i.e. remove all
references to SpecialProperty from the code and the JSON.
If I include SpecialProperty in the JSON but remove the custom
deserializer for it then it also works fine. The ctor for
SpecialProperty is called.
With the custom deserializer it doesn't work. The ctor for SpecialProperty is called but the custom deserializer is not.
What am I doing wrong?
#JsonDeserialize annotation can be placed on a field, a setter or a class. Jackson will take it into account if what is annotated is what it uses to set the value.
E.g.1 It will notice #JsonDeserialize over a setter if it uses the setter to set the value of a field.
E.g.2 It will notice #JsonDeserialize over a field if it directly sets this field without using a setter or a constructor.
It will tend to take it into account if it's on a class unless it's overridden by a more specific annotation on a field or setter docs. I reckon the docs could be clearer on the above details.
In your case you have the annotation over the SpecialProperty field but you are setting this field in the MyClass constructor so it's ignored.
In this case you can move #JsonDeserialize over the class instead of over the field. That's probably the simplest solution in your case. E.g.
#JsonDeserialize(using = MyClass.SpecialPropertyDeserializer.class)
private static class SpecialProperty {
Or you can skip the annotation altogether and register the deserializer on the mapper. First make SpecialProperty and SpecialPropertyDeserializer non private in MyClass and then:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(MyClass.SpecialProperty.class, new MyClass.SpecialPropertyDeserializer());
objectMapper.registerModule(module);
You can also get rid of constructor of MyClass and the current annotation over the SpecialProperty field will be taken into account.
I created custom JsonDeserializer for that can be applied to any field with type String.
public class EmptyToNullStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String text = jp.getText();
return "" == text ? null : text;
}
}
It can be used in models.
class SomeClass {
#JsonDeserialize(using = EmptyToNullStringDeserializer.class)
private String someField;
}
It converts JSON
{"someField": ""}
into Java object where someField equals to null (not "")
Question: How to create generic JsonDeserializer that sets null to all Java object fields that equals to "" in JSON?
It should be used as:
#JsonDeserialize(using = EmptyToNullStringDeserializer.class)
class SomeClass {
private String someField;
}
This more of a Jackson question than a Spring question. You would just need to register your custom deserializer with the ObjectMapper...
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addDeserializer(String.class, new EmptyToNullStringDeserializer());
mapper.registerModule(module);
How you get access to that ObjectMapper depends on if you are using Spring Boot or just plain Spring.
You need to register your custom deserializer with ObjectMapper
i have an class with the following annotations:
class A {
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() {
...
}
#JsonIgnore
public void setReferences(Map<String,List<String>>) {
}
...
}
}
What I try is to ignore the json on deserialization. But it doesn't work. Always when JSON String arrives the Jackson lib fill the references attribute. If I use only the #JsonIgnore annotation the getter doesn't work. Are there any solutions for this problem?
Thanks
I think there are two key pieces that should enable you to have "read-only collections" as desired. First, in addition to ignoring the setter, ensure that your field is also marked with #JsonIgnore:
class A {
#JsonIgnore
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() { ... }
#JsonIgnore
public void setReferences(Map<String,List<String>>) { ... }
}
Second, in order to prevent the getters from being used as setters, disable the USE_GETTERS_AS_SETTERS feature:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
As of Jackson 2.6, there is a new and improved way to define read-only and write-only properties, using JsonProperty#access() annotation. This is recommended over use of separate JsonIgnore and JsonProperty annotations.
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Map<String,List<String>> references;
You have to make sure there is #JsonIgnore annotation on the field level as well as on the setter, and getter annotated with #JsonProperty.
public class Echo {
#Null
#JsonIgnore
private String doNotDeserialise;
private String echo;
#JsonProperty
public String getDoNotDeserialise() {
return doNotDeserialise;
}
#JsonIgnore
public void setDoNotDeserialise(String doNotDeserialise) {
this.doNotDeserialise = doNotDeserialise;
}
public String getEcho() {
return echo;
}
public void setEcho(String echo) {
this.echo = echo;
}
}
#Controller
public class EchoController {
#ResponseBody
#RequestMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public Echo echo(#RequestBody #Valid Echo echo) {
if (StringUtils.isEmpty(echo.getDoNotDeserialise())) {
echo.setDoNotDeserialise("Value is set by the server, not by the client!");
}
return echo;
}
}
If you submit a JSON request with a “doNotDeserialise” value set to something, when JSON is deserialised to an object it will be set to null (if not I put a validation constraint on the field so it will error out)
If you set the “doNotDeserialise” value to something on the server then it will be correctly serialised to JSON and pushed to the client
I used #JsonIgnore on my getter and it didn't work and I couldn't configure the mapper (I was using Jackson Jaxrs providers). This worked for me:
#JsonIgnoreProperties(ignoreUnknown = true, value = { "actorsAsString",
"writersAsString", "directorsAsString", "genresAsString" })
I can only think of a non-jackson solution, to use a base class that does not have references for the mapping and then cast to the actual class:
// expect a B on an incoming request
class B {
// ...
}
// after the data is read, cast to A which will have empty references
class A extends B {
public Map<String,List<String>> references;
}
Why do you even send the References if you don't want them?
Or is the incoming data out of your hands and you just want to avoid the mapping exception telling you that jackson cannot find a property to set for incoming references? For that we use a base class which all of our Json model classes inherit:
public abstract class JsonObject {
#JsonAnySetter
public void handleUnknown(String key, Object value) {
// for us we log an error if we can't map but you can skip that
Log log = LogFactory.getLog(String.class);
log.error("Error mapping object of type: " + this.getClass().getName());
log.error("Could not map key: \"" + key + "\" and value: \"" + "\"" + value.toString() + "\"");
}
Then in the POJO you add #JsonIgnoreProperties so that incoming properties will get forwarded to handleUnknown()
#JsonIgnoreProperties
class A extends JsonObject {
// no references if you don't need them
}
edit
This SO Thread describes how to use Mixins. This might be the solution, if you want to keep your structure exactly as it is, but I have not tried it.