2

I have the following classes:

public class Container{
    String description;
    Element1 element1;
    Element2 element2;
}

public class Element1{
    String f11;
    String f12;
}

public class Element2{
    String f21;
    String f22;
}

I serialize Container to json format. For json serialization I use Jackson.

Now I want don't include element1 and element2 into json if f11, f12 and f21, f22 respectevily is blank.

As I understan I should write cistom serializer but I don't understand for which entity. Element or Container ? and how ?

P.S.

My question is not duplicate of Ignoring new fields on JSON objects using Jackson

At this topic explains how to ignore null value inside Container. My question about how to ignore object inside Container which has only null values inside

Community
  • 1
  • 1
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • This might help (http://stackoverflow.com/questions/25603581/using-conditions-to-dynamically-exclude-pojo-property-in-jackson-json-serializat). – Harry Cho Jun 23 '15 at 17:54
  • possible duplicate of [Jackson serialization: ignore empty values (or null)](http://stackoverflow.com/questions/16089651/jackson-serialization-ignore-empty-values-or-null) – Raman Shrivastava Jun 23 '15 at 17:56
  • @Raman Shrivastava please read update. My question is different – gstackoverflow Jun 23 '15 at 18:07

4 Answers4

3

An additional idea inspired by StaxMan's answer is to implement an "empty element module" that encapsulates all of the overrides to isEmpty for the different elements in the Container. For example the module could look something like this:

public class EmptyElementModule extends SimpleModule {
    private static final String NAME = "EmptyElementModule";

    public EmptyElementModule() {
        super(NAME);
        setSerializerModifier(new EmptyElementSerializerModifier());
    }

    public static class EmptyElementSerializerModifier extends BeanSerializerModifier {
        @Override
        public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
            Class<?> beanClass = beanDesc.getBeanClass();
            if (beanClass == Element1.class) {
                return new EmptyElement1Serializer((JsonSerializer<Object>) serializer);
            } else if (beanClass == Element2.class) {
                // return element 2 serializer
            }
            return serializer;
        }
    }

    public static class EmptyElement1Serializer extends JsonSerializer<Element1> {
        private final JsonSerializer<Object> defaultSerializer;

        public EmptyElement1Serializer(JsonSerializer<Object> defaultSerializer) {
            this.defaultSerializer = checkNotNull(defaultSerializer);
        }

        @Override
        public void serialize(Element1 value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            defaultSerializer.serialize(value, gen, serializers);
        }

        @Override
        public boolean isEmpty(SerializerProvider provider, Element1 value) {
            return value.f11 == null && value.f12 == null;
        }
    }
}
Community
  • 1
  • 1
Sam Berry
  • 7,394
  • 6
  • 40
  • 58
2

Although you probably do need a custom serializer, the method that you need to override there is isEmpty(...); and then specify inclusion strategy for property like so:

@JsonInclude(JsonInclude.Include.NON_EMPTY)

This will let the "owning" object determine that serialization is to be avoided: otherwise custom serializer will be called and it MUST write a value -- you could write null of course, but custom serializer itself cannot just decline to write anything because property name has already been written by caller.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • please provide a bit more details – gstackoverflow Jun 23 '15 at 21:45
  • Was not aware of the isEmpty(...) method. That is a much cleaner solution compared to redefining serialize(...). – Sam Berry Jun 23 '15 at 22:18
  • When I extends from **JsonSerializer** I should override **serialize** method because it abstract – gstackoverflow Jun 24 '15 at 10:11
  • Please advise which class should I extend to override only **isEmpty** method – gstackoverflow Jun 24 '15 at 10:22
  • I think a down side to this approach is that you would need to define a custom serializer for every element type. The OP mentioned in a comment that they have 10+ element types in this container. I think regardless you are writing config specific for each element unless they happen to all extend a common base. – Sam Berry Jun 24 '15 at 18:49
  • I added an answer here that I think is along the lines of what StaxMan is recommending (with the addition of `@JsonInclude` to your container for this example): http://stackoverflow.com/a/31034525/1756430 – Sam Berry Jun 24 '15 at 18:52
  • @gstackoverflow Yes, you also have to implement actual serialization, not just isEmpty(). – StaxMan Jun 24 '15 at 19:22
0

To ignore the elements in the container that have only null/empty members (the container's members, members) you can create a custom serializer for Container. Custom serializers are great because they give you ultimate control of how an object is written, but this can also lead to code that is difficult to maintain. I say this because with Jackson you can accomplish practically anything with a custom de/serializer, but there is usually a cleaner way (through annotations for example) for most problems. For this, the OP requested guidance on writing a custom serializer, and I don't know a simpler way to accomplish the desired behavior.

Create a Module and Custom Serializer

A Jackson Module is used to encapsulate a deserializer and serializer for a single object. For this example only a serializer is needed, but if you wanted to add a deserializer for Container you could just add it alongside the serializer inside of ContainerModule. The following code defines a serializer for Container that will only write element1 and element2 if at least one of their members are non-null:

public class ContainerModule extends SimpleModule {
    private static final String NAME = "ContainerModule";

    public ContainerModule() {
        super(NAME);
        addSerializer(Container.class, new ContainerSerializer());
    }

    public static class ContainerSerializer extends JsonSerializer<Container> {
        @Override
        public void serialize(Container container, JsonGenerator jg, SerializerProvider serializers) throws IOException {
            if (container != null) {
                // start object and write description
                jg.writeStartObject();
                jg.writeStringField("description", container.description);

                // only write Element1 if one of its members exist
                Element1 element1 = container.element1;
                if (element1 != null) {
                    if (!Strings.isNullOrEmpty(element1.f11)
                            && !Strings.isNullOrEmpty(element1.f12)) {
                        jg.writeFieldName("element1");
                        jg.writeObject(element1);
                    }
                }

                // only write Element2 if one of its members exist
                Element2 element2 = container.element2;
                if (element2 != null) {
                    if (!Strings.isNullOrEmpty(element2.f21)
                            && !Strings.isNullOrEmpty(element2.f22)) {
                        jg.writeFieldName("element2");
                        jg.writeObject(element2);
                    }
                }

                // close the Container object
                jg.writeEndObject();
            }
        }
    }
}

Register the Custom Serializer

The serializer can be registered globally on the ObjectMapper or on the class using @JsonDeserialize.

ObjectMapper Registration

ObjectMapper om = new ObjectMapper()
        .registerModule(new ContainerModule());

Class-level Registration

@JsonSerialize(using = ContainerModule.ContainerSerializer.class)
public class Container {
    String description;
    Element1 element1;
    Element2 element2;
}

This should produce the following results:

// Full container
{
  "description" : "an awesome description",
  "element1" : {
    "f11" : "f11 value",
    "f12" : "f12 value"
  },
  "element2" : {
    "f21" : "f21 value",
    "f22" : "f22 value"
  }
}

// Null Element1 properties
{
  "description" : "an awesome description",
  "element2" : {
    "f21" : "f21 value",
    "f22" : "f22 value"
  }
}

// Null Element2 properties
{
  "description" : "an awesome description",
  "element1" : {
    "f11" : "f11 value",
    "f12" : "f12 value"
  }
}

// Null Element1 and 2 properties
{
  "description" : "an awesome description"
}
Sam Berry
  • 7,394
  • 6
  • 40
  • 58
-1

If you use annotations this should work fine:

public class Container{
    String description;
    @JsonIgnore
    Element1 element1;
    @JsonIgnore
    Element2 element2;
}

So Jackson ignores these two where @JsonIgnore is annotated

CodeFox
  • 447
  • 4
  • 12