Suppress wrapper object when serializing Java object into JSON using Jackson
I have a web service that returns a list as JSON. It uses Jackson to map a List of Java POJOs into JSON. The problem is that the JSON representation has a wrapper object around the array, and I just want the array. I.e., I'm getting this:
{"optionDtoList":[{...}, ..., {...}]}
when what I really want is this:
[{...}, ..., {...}]
I am serializing the Java List directly; I'm not wrapping the List with a wrapper object and serializing a wrapper object. It's Jackson that seems to be adding the JavaScri开发者_运维技巧pt wrapper object.
I assume there's some annotation I can use on the POJO to suppress the wrapper object but I'm not seeing it.
Constraints on solution
I'd like to fix this on the service side rather than peeling off the wrapper on the client. The client is a jQuery UI widget (the autocomplete widget, not that it matters) that expects a simple array and I don't want to modify the widget itself.
What I've tried
- I tried replacing the List of Java POJOs with an array of Java POJOs and the result is the same.
- I tried
@JsonTypeInfo(use = Id.NONE)
thinking that that might suppress the wrapper, but it didn't.
In a test mode when I run:
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
String json = mapper.writeValueAsString( Arrays.asList("one","two","three","four","five") );
System.out.println(json);
returns:
["one","two","three","four","five"]
which is the behavior you are expecting right?
I could see that when I return this list via a Spring controller and let MappingJacksonJsonView handle transforming the list to a json, then yes there is a wrapper around it, which tells me that the MappingJacksonJsonView is the one adding the wrapper. One solution then would be to explicitly return the json from your controller, say:
@RequestMapping(value = "/listnowrapper")
public @ResponseBody String listNoWrapper() throws Exception{
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(Arrays.asList("one","two","three","four","five"));
}
I get the same problem as you.
After add @ResponseBody in front of my list in my method declaration, the problem was solved.
eg :
public @ResponseBody List<MyObject> getObject
You could write custom serializer:
public class UnwrappingSerializer extends JsonSerializer<Object>
{
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
JavaType type = TypeFactory.type(value.getClass());
getBeanSerializer(type, provider).serialize(value, new UnwrappingJsonGenerator(jgen), provider);
}
private synchronized JsonSerializer<Object> getBeanSerializer(JavaType type, SerializerProvider provider)
{
JsonSerializer<Object> result = cache.get(type);
if (result == null) {
BasicBeanDescription beanDesc = provider.getConfig().introspect(type);
result = BeanSerializerFactory.instance.findBeanSerializer(type, provider.getConfig(), beanDesc);
cache.put(type, result);
}
return result;
}
private Map<JavaType, JsonSerializer<Object>> cache = new HashMap<JavaType, JsonSerializer<Object>>();
private static class UnwrappingJsonGenerator extends JsonGeneratorDelegate
{
UnwrappingJsonGenerator(JsonGenerator d)
{
super(d);
}
@Override
public void writeEndObject() throws IOException, JsonGenerationException
{
if (depth-- >= yieldDepth) {
super.writeEndObject();
}
}
@Override
public void writeFieldName(SerializedString name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
@Override
public void writeFieldName(String name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
@Override
public void writeStartObject() throws IOException, JsonGenerationException
{
if (++depth >= yieldDepth) {
super.writeStartObject();
}
}
private int depth;
private final int yieldDepth = 2;
}
}
It will ignore outer objects on depth lower than specified (2 by default).
Then use it as follows:
public class UnwrappingSerializerTest
{
public static class BaseT1
{
public List<String> getTest()
{
return test;
}
public void setTest(List<String> test)
{
this.test = test;
}
private List<String> test;
}
@JsonSerialize(using = UnwrappingSerializer.class)
public static class T1 extends BaseT1
{
}
@JsonSerialize(using = UnwrappingSerializer.class)
public static class T2
{
public BaseT1 getT1()
{
return t1;
}
public void setT1(BaseT1 t1)
{
this.t1 = t1;
}
private BaseT1 t1;
}
@Test
public void test() throws IOException
{
ObjectMapper om = new ObjectMapper();
T1 t1 = new T1();
t1.setTest(Arrays.asList("foo", "bar"));
assertEquals("[\"foo\",\"bar\"]", om.writeValueAsString(t1));
BaseT1 baseT1 = new BaseT1();
baseT1.setTest(Arrays.asList("foo", "bar"));
T2 t2 = new T2();
t2.setT1(baseT1);
assertEquals("{\"test\":[\"foo\",\"bar\"]}", om.writeValueAsString(t2));
}
}
Notes:
- It expects only single field wrapper and will generate invalid JSON on something like
{{field1: {...}, field2: {...}}
- If you use custom
SerializerFactory
you probably will need to pass it to the serializer. - It uses separate serializer cache so this also can be an issue.
Honestly, I wouldn't be too quick to try to fix this problem as having the wrapper does create a situation where your code is more extensible. Should you expand this in the future to return other objects, your clients consuming this web service most likely won't need to change the implementation.
However, if all clients expect an array that is unnamed, adding more properties in the future outside of that array may break the uniform interface.
With that said, everyone has their reasons for wanting to do something a certain way. What does the object look like that you are serializing? Are you serializing an object that contains an array, or are you serializing the actual array itself? If your POJO contains an array, then maybe the solution is to pull the array out of the POJO and serialize the array instead.
I stumbled upon this question while trying to solve the same problem, but was not using this with a @ResponseBody method, but was still encountering the "wrapper" in my serialized JSON. My solution was to add @JsonAnyGetter to the method/field, and then the wrapper would disappear from the JSON.
Apparently this is a known Jackson bug/workaround: http://jira.codehaus.org/browse/JACKSON-765.
精彩评论