1

In my web application that is using Spring, we want use a custom JSON structure. Spring by default takes a POJO like this:

public class Model {

    private int id;
    private String name;

    public Model(){}

    public Model(int id, String name){
        this.id = id;
        this.name = name;
    }
}

and turns it into this:

{"id":1, "name":"Bob"}

With our application, we want to turn it into this instead:

[1, "Bob"]

I want to use Spring's default serialization logic that detects the Java type (int, String, Collection, etc.) and maps to the appropriate JSON type, but just change the wrapping object to an array rather than and object with fields.

This is the Serializer I have so far (which will be implemented in the model with @JsonSerialize(using = Serializer.class)), but would prefer not to rewrite all the logic Spring already has implemented.

public class Serializer extends JsonSerializer<Model> {
    @Override
    public void serialize(Model value, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {

        jgen.writeStartArray();
        jgen.writeString(value.id);
        .... other values ...
        jgen.writeEndArray();

    }
}

How can I hook into the pre-existing Serializer so that this new serializer will work with any POJO as the default one does (not just the Model class, but any similar or child class we need to serialize to an array)? This could have mixed properties and no specific naming convention for the properties.

I want to avoid writing a custom serializer for every different Model class (the ... other values ...) section.

Cameron
  • 13
  • 5
  • 1
    The serializer is the correct way to do it. – Sotirios Delimanolis Mar 18 '15 at 21:33
  • Ah, yes. I need to clarify, as I want the serializer to not be specific to the one Model class, but work with any POJO and dynamically build the array accordingly. Thanks! – Cameron Mar 18 '15 at 21:53
  • 1
    So instead of `{"id":1}`, you want `[1]`, not `{"id":[1]}`? – Sotirios Delimanolis Mar 18 '15 at 21:54
  • Correct. Or if I pass a Model that has an id and a name property: [1, "Bob"] – Cameron Mar 18 '15 at 21:56
  • 1
    I don't think you'll find a generic way to do this. This is a very specific requirement (and I suggest against it). There is no Jackson annotation that will give you that behavior. As for serializers, you'll need to write a different one for each type, assuming the types differ in their fields. – Sotirios Delimanolis Mar 18 '15 at 21:57
  • Spring does this out of the box, as I can return any model class from a controller method and have it serialized to JSON accordingly. Underneath it has to be matching type checks to the correct generator.write* method, unless I'm very mistaken as to how it's serializing by default, and I'm interested in re-using that same logic if possible. – Cameron Mar 18 '15 at 22:02
  • 1
    Right, the defaults are very specific. An array or `Collection` type gets serialized to a JSON array. `String` gets serialized to a JSON string, `Number` types get serialized to JSON numbers, `null` gets serialied to JSON null, and other reference types get serialized to JSON objects. You're trying to serialize a reference type (`Model`) to a JSON array where with a single element. This is very non standard. – Sotirios Delimanolis Mar 18 '15 at 22:22
  • 1
    What about nested POJOs? How would those be serialized? – fps Mar 18 '15 at 23:46
  • Don't do it man. It's actually a security risk. See http://stackoverflow.com/questions/3503102/what-are-top-level-json-arrays-and-why-are-they-a-security-risk – Neil McGuigan Mar 19 '15 at 00:19

2 Answers2

0

You could use @JsonValue annotation for this. Example:

public class Model {

  private int id;

  public Model(){}

  public Model(int id){
    this.id = id;
  }

  @JsonValue
  public int[] getValue() {
    return new int[]{this.id};
  } 
}
  • This would work for the one specific instance. I've updated the question to clarify I'm looking for a more generic solution. Thanks! – Cameron Mar 18 '15 at 22:31
  • In such case i would use mixin annotations (http://wiki.fasterxml.com/JacksonMixInAnnotations). You can create an interface (or abstract class) with value method annotated with `@JsonValue` and implement this mixin interface in all of your classes. Moreover you can just specify this mixin in the `ObjectMapper`'s configuration and do not actually change your own classes. – Advanced Mar 19 '15 at 23:53
0

Take a look at Apache BeanUtils library, in particular, pay attention to the BeanUtils.populate() method.

What that method does is to convert any given Object to a Map<String, Object>, based on JavaBeans conventions. In the keys you'd have the attribute names, while in the values you'd have every attribute's value. That method should be enough for standard cases. Read the documentation carefully, to check how to handle special cases.

Model model = ...; // get your model from some place

Map<String, Object> properties = new HashMap<>();

BeanUtils.populate(model, properties);

// Exception handling and special cases left as an excercise

The above recursively fills the properties map, meaning that if your Model has an attribute named otherModel whose type is OtherModel, then the properties map will have another map at the entry that matches the otherModel key, and so on for other nested POJOs.

Once you have the properties map, what you want to serialize as the elements of your array will be in its values. So, something like this should do the job:

public List<Object> toArray(Map<String, Object> properties) {
    List<Object> result = new ArrayList<>();

    for (Object obj : properties.values()) {
        Object elem = null;
        if (obj != null) {
            Class<?> clz = obj.getClass();
            if (Map.class.isAssignableFrom(clz)) {
                elem = toArray((Map<String, Object>) obj); // recursion!
            } else {
                elem = obj;
            }
        }
        result.add(elem); // this adds null values
                          // move 1 line up if you don't
                          // want to serialize nulls
    }
    return result;
}

Then, after invoking the toArray() method, you'd have a List<Object> ready to serialize using the standard Spring mechanisms. I even believe you won't need a specific serializer:

List<Object> array = toArray(properties);
return array; // return this array, i.e. from a Controller 

Disclaimer:

Please use this as a guide and not as a final solution. I tried to be as careful as possible, but the code might have errors. I'm pretty sure it needs special handling for arrays and Iterables of POJOs. It's undoubtedly lacking exception handling. It works only for POJOs. It might explode if the supplied object has circular references. It's not tested!

fps
  • 33,623
  • 8
  • 55
  • 110