I want to both use a custom serializer and have the JsonInclude.Include.NON_DEFAULT designation be honored. When I don't use a custom serializer it is honored but when I do use a custom serializer it is not.
This is Jackson 2.2.2. I do not presently have the option to switch to a newer version of Jackson.
Here's a simple example that shows the problem:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
public class JacksonSerialization
{
public static void main(String[] args) throws Exception {
ObjectMapper serializer = new ObjectMapper();
Foo foo = new Foo();
foo.setFlags(EnumSet.of(Flag.CC, Flag.BB));
System.out.println(serializer.writeValueAsString(foo));
foo = new Foo();
System.out.println(serializer.writeValueAsString(foo));
}
public static enum Flag
{
AA,
BB,
CC
}
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public static class Foo
{
private Set<Flag> flags;
public Foo() {
flags = EnumSet.of(Flag.AA);
}
#JsonGetter("f")
#JsonSerialize(using = FlagSetSerializer.class)
public Set<Flag> getFlags() {
return flags;
}
public void setFlags(Set<Flag> theFlags) {
flags = theFlags;
}
}
public static class FlagSetSerializer extends JsonSerializer<Set<Flag>>
{
#Override
public void serialize(Set<Flag> value,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
String csv = value.stream()
.map(Flag::toString)
.collect(Collectors.joining(","));
jsonGenerator.writeString(csv);
}
}
}
And here's the output:
{"f":"BB,CC"}
{"f":"AA"}
Note that f is being serialized even when it has the default value. If I comment out the #JsonSerialize annotation then I get the following output:
{"f":["BB","CC"]}
{}
Then f properly does not get serialized. But of course things are not being serialized in the format I want.
So how do I get the custom serializer to honor the class's #JsonInclude annotation?
You probably want to implement public boolean isEmpty(SerializerProvider provider, T value) as per the documentation, which says:
public boolean isEmpty(SerializerProvider provider, T value)
Method called to check whether given serializable value is considered
"empty" value (for purposes of suppressing serialization of empty
values).
Default implementation will consider only null values to be
empty.
As per https://github.com/FasterXML/jackson-databind/issues/730
Another possible source of trouble is that you talk about NON_EMPTY but you code uses NON_DEFAULT.
And rather too much digging in the debugger leads me to suggest
#JsonSerialize(using = FlagSetSerializer.class, include = JsonSerialize.Inclusion.NON_DEFAULT)
Which seems to pass your tests.
The problem seems to be in JacksonAnnotationInspector#findSerializationInclusion, which first looks for a #JsonInclude attribute on the property, and when it fails to find that, it looks for a #JsonSerialize annotation. #JsonSerialize includes a deprecated include property, which has a default value of ALWAYS.
I've not looked into it too deeply, but I suspect a deprecation/refactor managed to slice off some functionality. C'est la vie.
Related
I'm trying to understand what Gson is doing here.
Here's my simple test case.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.junit.Test;
import java.io.IOException;
import java.time.ZoneId;
public class TestSuite
{
public class TestItem
{
private ZoneId TimeZone = ZoneId.of("America/New_York");
}
class ZoneIdAdapter extends TypeAdapter<ZoneId>
{
#Override
public void write(final JsonWriter jsonWriter, final ZoneId timeZone) throws IOException
{
jsonWriter.value(timeZone.getId());
}
#Override
public ZoneId read(final JsonReader jsonReader) throws IOException
{
return ZoneId.of(jsonReader.nextString());
}
}
#Test
public void canSerializeTestItem()
{
Gson gson = new GsonBuilder()
.registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
.create();
gson.toJson(new TestItem());
}
}
For me using Java 17 and Gson 2.10 that fails with the following:
com.google.gson.JsonIOException: Failed making field 'java.time.ZoneRegion#id' accessible; either increase its visibility or write a custom TypeAdapter for its declaring type.
I'm failing to understand why gson is concerning itself with java.time.ZoneRegion#id when I thought* I already told it how to serialize a ZoneId (I'm guessing ZoneRegion is some internal to to java.time.ZoneId? I can't seem to find it.).
And what's more confusing to me, is that if I change to the following:
public class TestItem
{
#Expose
private ZoneId timeZone = ZoneId.of("America/New_York");
}
// ... omitted other stuff
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(ZoneId.class, new ZoneIdAdapter())
.create();
gson.toJson(new TestItem());
It then works as I would expect it to (doesn't error).
Is there a better way to 'teach' gson how to serialize the third party types I'm concerned with?
The actual concrete type that ZoneId.of() returns is a ZoneRegion, and registerTypeAdapter only registers an adapter for a single type.
As ZoneRegion is not a public class you can't (easily) register an adapter for it -- and of course you want a configuration which works even if you have an instance of TestItem where timeZone has a different concrete type.
You can use registerTypeHierarchyAdapter instead. That registers an adapter for a class and all of its subclasses.
I don't know why #Expose changes the behaviour.
With the following Java code:
public class Bean{
private String value;
public Bean(#NonNull String value) {
//Usually fail-fast validation can be added here if it is needed
this.value = value;
}
public String getValue() {return this.value;}
}
Is it possible to check the constructor argument value by means of the annotation, #NonNull at run time other than compile time? Personally I still did not find any checker-framework, which can do validation checking at run time. However, is it possible to implement an Annotation processor to do run time checking?
You should take a look at #NotNull from javax.validation.constraints.
I use it in my models and it throw a Constraint exception when I try to save a model with a null #NotNull value.
The import is import javax.validation.constraints.NotNull;
If you are using Spring and mongodb, you'll have to configure it so it works, I have found a piece of code somewhere on the Internet (can't remember where), you may use it:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
public class CustomRepositoryRestConfigurerAdapter {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener(
#Qualifier("localValidatorFactoryBean") LocalValidatorFactoryBean lfb
) {
return new ValidatingMongoEventListener(lfb);
}
}
Yes. Lombok's #NonNull is a runtime check which just inserts an if-statement with a throw:
With Lombok
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
Vanilla Java
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked #NonNull but is null");
}
this.name = person.getName();
}
}
Misconception at your end: there is no single answer to your question.
Some annotations, when used on source code like this are mainly targeting compile time. Like some static analysis tool that analyses the data flow to tell you that you are violating "annotated" contracts here or there.
But some annotations are also "meant" to be used at runtime, for example to be used with "beans". Such objects might come in as parameter of a HTTP request, and then you have some framework checking if the content received as JSON for example is actually valid, according to the rules specified via annotations. See this tutorial for some examples.
My domain Objects are enhanced using lombok, which generates the java.beans #ConstructorProperties annotation for the constructors of immutable objects.
Now in my frontend artifact, I'd like to serialize these objects to JSON using Jackson 2.
For Jackson 1, this could be done using Jackson Extensions. Is there such a solution for Jackson 2 as well or do I have to write it myself?
My main problem is that I want to keep my domain Objects frontend agnostic, so I wouldn't like to pollute them with Jackson annotations.
And no: Java 8 parameter names is not an option, as I am stuck with Java 7 for the time being.
Sean Patrick Floyd has already written a solution, but I am posting my solution because his is proprietary. This is a Jackson module that uses an AnnotationIntrospector to make a constructor annotated with #ConstructorProperties a jackson #JsonCreator.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
public class ConstructorPropertiesModule extends SimpleModule {
public ConstructorPropertiesModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ConstructorPropertiesAnnotationIntrospector());
}
public static class ConstructorPropertiesAnnotationIntrospector extends NopAnnotationIntrospector {
#Override
public boolean hasCreatorAnnotation(Annotated a) {
if (!(a instanceof AnnotatedConstructor)) {
return false;
}
AnnotatedConstructor ac = (AnnotatedConstructor) a;
Constructor<?> c = ac.getAnnotated();
ConstructorProperties properties = c.getAnnotation(ConstructorProperties.class);
if (properties == null) {
return false;
}
for (int i = 0; i < ac.getParameterCount(); i++) {
final String name = properties.value()[i];
final int index = i;
JsonProperty jsonProperty = new JsonProperty() {
#Override
public String value() {
return name;
}
#Override
public boolean required() {
return false;
}
#Override
public Class<? extends Annotation> annotationType() {
return JsonProperty.class;
}
#Override
public int index() {
return index;
}
};
ac.getParameter(i).addOrOverride(jsonProperty);
}
return true;
}
}
}
The module can then be registered to an object mapper to deserialize JSON using the #ConstructorProperties annotation:
ObjectMapper m = new ObjectMapper();
m.registerModules(new ConstructorPropertiesModule());
As others stated Jackson now supports #ConstructorProperties - unfortunatelly. Because it messed up things.
The logic Jackson applies is quite unfortunate. If multiple #ConstructorProperties annotated constructor are present it will create the object via the one with most parameters. Ops. This is problem especially with Lombok which annotates all constructors with #ConstructorProperties. But anyway, this annotation is not there solely for Jackson. It makes sense to annotate every single constructor for any code inspection tool which may use this information. Lombok is right here.
Imagine following object:
#Data
#Builder
#NoArgsConstructor // for Jackson
#AllArgsConstructor // for builder
public class MyDto {
private Type1 value1 = Type1.NONE;
private Type2 value2;
}
Here Jackson will always use the all-args constructor because it is annotated with #ConstructorProperties and has most parameters.
This also means that if you set only value2 in your JSON object the value1 becomes null. Not what you would expect.
Conclusion: the current behaviour (when used with Lombok or annotate more than one constructor) doesn't allow for the easy class-level default values.
Workaround: #AllArgsConstructor(suppressConstructorProperties=true) - but this is claimed to be deprecated soon as it's present just for java 1.5 compatibility purposes.
This issue has finally been resolved in Jackson 2.7 and #ConstructorProperties are now supported out-of-the-box.
See https://github.com/FasterXML/jackson-databind/issues/905
I'm afraid you will have to write a similar wrapper for Jackson2 yourself.
I have a a Map<String,Foo> foosMap that I want to serialize through Jackson . Now I want following two settings on the serialization process:
The Map can have have plenty of null values and null keys and I don't want nulls to be serialized.
For all those Foos that are getting serialized, I do not want to serialize null objects referenced inside Foo.
What is the best way to achieve this ? I am using jackson-core1.9 and jackson-mapper1.9 jars in my project.
If it's reasonable to alter the original Map data structure to be serialized to better represent the actual value wanted to be serialized, that's probably a decent approach, which would possibly reduce the amount of Jackson configuration necessary. For example, just remove the null key entries, if possible, before calling Jackson. That said...
To suppress serializing Map entries with null values:
Before Jackson 2.9
you can still make use of WRITE_NULL_MAP_VALUES, but note that it's moved to SerializationFeature:
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
Since Jackson 2.9
The WRITE_NULL_MAP_VALUES is deprecated, you can use the below equivalent:
mapper.setDefaultPropertyInclusion(
JsonInclude.Value.construct(Include.ALWAYS, Include.NON_NULL))
To suppress serializing properties with null values, you can configure the ObjectMapper directly, or make use of the #JsonInclude annotation:
mapper.setSerializationInclusion(Include.NON_NULL);
or:
#JsonInclude(Include.NON_NULL)
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To handle null Map keys, some custom serialization is necessary, as best I understand.
A simple approach to serialize null keys as empty strings (including complete examples of the two previously mentioned configurations):
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Map<String, Foo> foos = new HashMap<String, Foo>();
foos.put("foo1", new Foo("foo1"));
foos.put("foo2", new Foo(null));
foos.put("foo3", null);
foos.put(null, new Foo("foo4"));
// System.out.println(new ObjectMapper().writeValueAsString(foos));
// Exception: Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.getSerializerProvider().setNullKeySerializer(new MyNullKeySerializer());
System.out.println(mapper.writeValueAsString(foos));
// output:
// {"":{"bar":"foo4"},"foo2":{},"foo1":{"bar":"foo1"}}
}
}
class MyNullKeySerializer extends JsonSerializer<Object>
{
#Override
public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused)
throws IOException, JsonProcessingException
{
jsonGenerator.writeFieldName("");
}
}
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To suppress serializing Map entries with null keys, further custom serialization processing would be necessary.
For Jackson versions < 2.0 use this annotation on the class being serialized:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
Answer seems to be a little old, What I did was to use this mapper to convert a MAP
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false);
a simple Map:
Map<String, Object> user = new HashMap<String,Object>();
user.put( "id", teklif.getAccount().getId() );
user.put( "fname", teklif.getAccount().getFname());
user.put( "lname", teklif.getAccount().getLname());
user.put( "email", teklif.getAccount().getEmail());
user.put( "test", null);
Use it like this for example:
String json = mapper.writeValueAsString(user);
my solution, hope help
custom ObjectMapper and config to spring xml(register message conveters)
public class PyResponseConfigObjectMapper extends ObjectMapper {
public PyResponseConfigObjectMapper() {
disable(SerializationFeature.WRITE_NULL_MAP_VALUES); //map no_null
setSerializationInclusion(JsonInclude.Include.NON_NULL); // bean no_null
}
}
I have a legacy class, with a lot of public double fields. All double fields are initialized with Double.MAX_VALUE to indicate that they are empty. (The legacy serialization is coded to ignore the field and not serialize if field is equals to Double.MAX_VALUE).
We are now trying to serialize this class to Xml using JAXB Marshaller. It is working fine, except that we want to prevent generating Xml for fields which equal Double.MAX_VALUE.
We aren't using a separate JAXB schema, just marking up our classes with various javax.xml.bind.annotation Annotations. If a schema is used, you can add a <javaType> element to specify a custom DataType converter. Is there any way to do this using Annotations or programmatically?
After trying approach recommended below, I still can't get XmlAdapter picked up:
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=Double.class), #XmlJavaTypeAdapter(value=EmptyDoubleValueHandler.class, type=double.class)})
package tta.penstock.data.iserver;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
My top level class is: tta.penstock.data.iserver.OrderBlotter, which contains a list of tta.penstock.data.iserver.OrderResponseWrappers which extends com.eztech.OrderResponse. All the double fields are contained in com.eztech.OrderResponse.
My unit test code does the following:
JAXBContext context = JAXBContext.newInstance(new Class[] { OrderBlotter.class, OrderResponseWrapper.class, OrderResponse.class});
Marshaller marshaller = context.createMarshaller();
StringWriter stringWriter = new StringWriter();
marshaller.marshal(blotter, stringWriter);
System.out.println("result xml=\n" + stringWriter.toString());
But the double values still don't get handled by the XmlAdapter. I know I'm missing something basic, but I'm not sure what it is.
You could use an XmlAdapter:
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
The XmlAdapter
package example;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DoubleAdapter extends XmlAdapter<Double, Double>{
#Override
public Double unmarshal(Double v) throws Exception {
return v;
}
#Override
public Double marshal(Double v) throws Exception {
if(Double.MAX_VALUE == v) {
return null;
} else {
return v;
}
}
}
The Model Object
package example;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Root {
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double maxDouble = Double.MAX_VALUE;
#XmlJavaTypeAdapter(DoubleAdapter.class)
public Double aDouble = 123d;
}
Demo Code
package example;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(new Root(), System.out);
}
}
UPDATE
StaxMan's suggestion is a good one. If you specify the following package level annotation you can avoid the need of individually annotating all the Double properties
package-info.java
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type=Double.class, value=DoubleAdapter.class)
})
package example;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
Write a getter that returns null, instead of Double.MAX_VALUE? (if type is 'double', need to change it to 'Double' first, to allow nulls).
Since JAXB by default ignores writing out of nulls, that should achieve what you are trying to do. This assumes you can modify legacy class in question.