Friday, 5 May 2017

Custom Java Serializer for a POJO with Custom Annotation

Main goal that this blog gonna detail about writing a Custom Serializer for a POJO which has a Custom Annotation attributes. As a prerequisites would request users to have quick brushing on how to implement a Custom Annotation and write a Custom Serializer.  The following are few links that will give you quick overview on Custom Annotation and Custom Serializer respectively:
 The Main requirement that's been given to me is to develop a custom serializer and the class will have the following different kind of attributes:

  • String attribute with & without custom annotation
  • Primitive attribute with & without custom annotation
  • Primitive Wrapper attribute with & without custom annotation
  • POJO attribute with & without custom annotation 
The following is the Class that is been used to implement the Serializer:

public class CustomAnnotation {

    //String attribute with Annotation 
    @ConfigField(displayName = "AnnotationForString"
    description = "config field anotation Test", required = true,
    multiplicity = Multiplicity.ZERO_TO_MANY, expressionEnabled = false)
    private String testField = "something";

    //primitive attribute with Annotation     
    @ConfigField(displayName = "Long-field-test"
    description = "config field long", required = true,
    multiplicity = Multiplicity.ONE_TO_MANY,expressionEnabled = true)
    private long longField;

    //String attribute without Annotation 
    private String stringNoAnnotation;

    //no Annotation for primitive attribute     
    private long longFieldNoAnnotation;

    //Primitive Wrapper with Annotation     
    @ConfigField(displayName = "AnnotationForPrimitiveWrapper"
    description = "config field with primitive Wrapper"
    required = true, multiplicity = Multiplicity.ONE_TO_MANY,expressionEnabled = true)
    private Integer intField;

    //PrimitveWrapper without Annotation     
    private Integer primitiveWrapperNoAnnotation;

    //POJO with Annotation 
    @ConfigField(displayName = "pojo-annotation"
    description = "config field POJO", required = true,
    multiplicity = Multiplicity.SINGLE,expressionEnabled = false)
    private CustomChild customChild;

    //POJO with out Annotation     
    private CustomChild customChildNoAnnotation;

}

In the above code @ConfigField is a the Custom Annotation that is been developed and the following is the implementation of the same:

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigField {
    String displayName() default "";

    String description() default "";

    boolean required() default false;

    Multiplicity multiplicity() default SINGLE;

    boolean expressionEnabled() default false;
}

In the above code Multiplicity is a enum and it has the following definition:

public enum Multiplicity {
    SINGLE("1"),
    ZERO_TO_ONE("0..1"),
    ZERO_TO_MANY("0..*"),
    ONE_TO_MANY("1..*");

    private String value;

    public String getValue() {
        return value;
    }

    private Multiplicity(String value) {
        this.value = value;
    }
}
 
The following is the POJO(CustomChild) that is been used in the CustomAnnotation class:
 
public class CustomChild {

    private Integer childName;

    @ConfigField(displayName = "Custom child Annotation int"
    description = "Custom Child attribute with Annotation", required = false,
    multiplicity = Multiplicity.ZERO_TO_MANY,expressionEnabled = true)
    private int childInt;
}
 
Now we move on to the core part on how the custom Serializer is been implemented.
 
I have used jackson StdSerializer for writing the custom Serializer. 
I have leveraged reflection to get the declared fields of the class. And further used 
reflection to get the annoation and it's field.
 
The following is the code snippet for the same:
 
public void serialize(Object o, JsonGenerator jsonGenerator, 
SerializerProvider serializerProvider) throws IOException {

    jsonGenerator.writeStartObject();
    jsonGenerator.writeStringField("name", o.getClass().getSimpleName());
    jsonGenerator.writeArrayFieldStart("definition");

    Field[] fields = o.getClass().getDeclaredFields();
    for(Field field : fields) {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("name"
        StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(field.getName()), '-')
             .toString().toLowerCase());
        addAnnotationInJson(field, jsonGenerator);
        try {
            addTypeInJson(field, jsonGenerator);
        } catch (Exception e) {

        }
        jsonGenerator.writeEndObject();
    }
    jsonGenerator.writeEndArray();
    jsonGenerator.writeEndObject();

} 

private void addTypeInJson(Field field, JsonGenerator jsonGenerator) throws IOException, ClassNotFoundException {
    if(!ClassUtils.isPrimitiveOrWrapper(field.getType()) && !field.getType().getSimpleName().equalsIgnoreCase
            ("string")) {
        jsonGenerator.writeStringField("type", "complex");
        jsonGenerator.writeFieldName("children");
        jsonGenerator.writeStartArray();
        addChildInJson(field, jsonGenerator);
        jsonGenerator.writeEndArray();
    } else {
        jsonGenerator.writeStringField("type", field.getType().getSimpleName());
    }
}
 
 
With these code in place the following the Seralized output for the class that we have seen:
 
{
  "name" : "CustomAnnotation",
  "definition" : [ {
    "name" : "test-field",
    "displayName" : "AnnotationForString",
    "description" : "config field anotation Test",
    "required" : true,
    "multiplicity" : "0..*",
    "expressionEnabled" : false,
    "type" : "String"
  }, {
    "name" : "long-field",
    "displayName" : "Long-field-test",
    "description" : "config field long",
    "required" : true,
    "multiplicity" : "1..*",
    "expressionEnabled" : true,
    "type" : "long"
  }, {
    "name" : "string-no-annotation",
    "displayName" : "string no annotation",
    "type" : "String"
  }, {
    "name" : "long-field-no-annotation",
    "displayName" : "long field no annotation",
    "type" : "long"
  }, {
    "name" : "int-field",
    "displayName" : "AnnotationForPrimitiveWrapper",
    "description" : "config field with primitive Wrapper",
    "required" : true,
    "multiplicity" : "1..*",
    "expressionEnabled" : true,
    "type" : "Integer"
  }, {
    "name" : "primitive-wrapper-no-annotation",
    "displayName" : "primitive wrapper no annotation",
    "type" : "Integer"
  }, {
    "name" : "custom-child",
    "displayName" : "pojo-annotation",
    "description" : "config field POJO",
    "required" : true,
    "multiplicity" : "1",
    "expressionEnabled" : false,
    "type" : "complex",
    "children" : [ {
      "name" : "child-name",
      "displayName" : "child name",
      "type" : "Integer"
    }, {
      "name" : "child-int",
      "displayName" : "Custom child Annotation int",
      "description" : "Custom Child attribute with Annotation",
      "required" : false,
      "multiplicity" : "0..*",
      "expressionEnabled" : true,
      "type" : "int"
    } ]
  }, {
    "name" : "custom-child-no-annotation",
    "displayName" : "custom child no annotation",
    "type" : "complex",
    "children" : [ {
      "name" : "child-name",
      "displayName" : "child name",
      "type" : "Integer"
    }, {
      "name" : "child-int",
      "displayName" : "Custom child Annotation int",
      "description" : "Custom Child attribute with Annotation",
      "required" : false,
      "multiplicity" : "0..*",
      "expressionEnabled" : true,
      "type" : "int"
    } ]
  } ]
} 
 
 Working code can be found in https://github.com/vivek-dhayalan/customSerializer/ 
 

No comments:

Post a Comment