2

I have huge json document and appropriate jaskson models that are mapped to that json. In some cases it is not possible to build the required json document with all object because some data does not exist.

for instance I have following models:

class First {

    private firstField;
    private secondField;
    etc...
}

class Second {

    private firstField;
    private secondField;
    etc...
}

class General {

    private First first;
    private Second second;
    etc...
}

And there is possibility to populate only First instance:

In usual cases it will be serialized something like this:

{
   "first":{
      "firstField":"some_value",
      "secondField":"some_value"
   },
   "second":null
}

But my goal is to serialize General class something like this:

{  
   "first":{  
      "firstField":"some_value",
      "secondField":"some_value"
   },
   "second":{  
      "firstField":"null",
      "secondField":"null"
   }
}

It is possible to achieve this with following changes in General class in order to initialize their members using default constructors by default:

class General {

        private First first = new First();
        private Second second = new Second()
        etc...
    }

But this approach will cause too many changes around existing models and I am not sure that it is the best one approach. Is it possible to create some custom serializer that will do this by itself?

Edited according to https://stackoverflow.com/users/1898563/michael suggestion:

So, the main idea is to create serializer that would be able to check whether instance is null and if it is null it should be able to create new instance using default constructor, note: this serializer should not be based on specific Second class, it should work with any object that is going to be serialized except simple types.

fashuser
  • 2,152
  • 3
  • 29
  • 51

1 Answers1

2

Yes, its possible to create a custom serializer which uses reflection to do this. You can do this by extending StdSerializer. Your implementation might look something like this:

public class NullSerializer<T> extends StdSerializer<T> {

    public NullSerializer() {
        this(null);
    }

    public NullSerializer(Class<T> t) {
        super(t);
    }

    @Override
    public void serialize(T item, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonProcessingException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException)
    {

        jgen.writeStartObject();

        // For all fields of the class you're serializing
        for (final Field field : item.getClass().getDeclaredFields())
        {
            field.setAccessible(true);

            Object value = field.get(item);

            // if the value is null, create a new instance
            if (value == null)
            {  
                value = field.getType().getConstructor().newInstance();
            }

            // write it
            jgen.writeObject(value);
        }

        jgen.writeEndObject();
    }
}

This relies on there being a public default constructor for each of the fields. You may want to catch some of the exceptions rather than declare them as thrown in the signature as I've done.

You need to register this serializer with the ObjectMapper. This article explains how to do that.


I don't think this is a particularly elegant solution, but it should fulfill your requirements. I would avoid having nullable fields in the first place, but maybe that's not possible in your case.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • Thanks, for quick answer, probably my question wasn't describe in appropriate way, actually the main idea to have serializer that would be able to check any object except simple types whether it is null and if it equals null it should be able to create new objects using default constructors, so it shouldn't be based on some specific Second type, it should be able to work with each new object that will be serialized. – fashuser Aug 08 '17 at 11:00
  • No problem. Well you need to edit your question then. – Michael Aug 08 '17 at 11:01
  • Thanks for the tip, but do you know how it can be used around all models that I have, currently I have global ObjectMapper instance that is used for serialization and I going to adjust it to apply this serializer to all models, but it looks like using this approach I will need to specify each type that is going to be serialized though NullSerializer explicitly – fashuser Aug 08 '17 at 11:50
  • 1
    , something like this SimpleModule s = new SimpleModule(); s.addSerializer(Second.class, new NullSerializer());. And this approach won't work for me, because I have huge json model as was mentioned in the initial question and I would need to specify huge amount of models to use this serializer globally, also I would need always keep this list up to date. Is the any other approach to specify it globally? Many Thanks! – fashuser Aug 08 '17 at 11:50
  • 1
    Wouldn't you be doing `s.addSerializer(General.class ...` ? Anyway, you could loop through the fields with reflection in a similar way. – Michael Aug 08 '17 at 11:51
  • Ah, I see, let me try that. – fashuser Aug 08 '17 at 11:59
  • When I register the bean `NullSerializer does not define valid handledType() -- must either register with method that takes type argument or make serializer extend com.fasterxml.jackson.databind.ser.std.StdSerializer'` – anat0lius Oct 11 '18 at 10:23
  • @anat0lius Post a question and I'll happily take a look. – Michael Oct 11 '18 at 10:29
  • @Michael https://stackoverflow.com/questions/52759359/how-do-i-register-my-jsonserializer-on-spring – anat0lius Oct 11 '18 at 11:56