10

我有一个以 JSON 形式返回列表的 Web 服务。它使用 Jackson 将 Java POJO 列表映射到 JSON。问题是 JSON 表示在数组周围有一个包装器对象,我只想要数组。即,我得到这个:

{"optionDtoList":[{...}, ..., {...}]}

当我真正想要的是:

[{...}, ..., {...}]

我正在直接序列化 Java 列表;我没有用包装器对象包装列表并序列化包装器对象。似乎是 Jackson 正在添加 JavaScript 包装器对象。

我假设我可以在 POJO 上使用一些注释来抑制包装器对象,但我没有看到它。

解决方案的限制

我想在服务端解决这个问题,而不是在客户端剥离包装。客户端是一个 jQuery UI 小部件(自动完成小部件,并不重要),它需要一个简单的数组,我不想修改小部件本身。

我试过的

  • 我尝试用 Java POJO 数组替换 Java POJO 列表,结果是一样的。
  • 我试图@JsonTypeInfo(use = Id.NONE)认为这可能会抑制包装器,但事实并非如此。
4

5 回答 5

6

在我运行时的测试模式下:

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);

返回:

["one","two","three","four","five"]

这是您期望的行为吗?

我可以看到,当我通过 Spring 控制器返回此列表并让 MappingJacksonJsonView 处理将列表转换为 json 时,是的,它周围有一个包装器,它告诉我 MappingJacksonJsonView 是添加包装器的那个。然后一种解决方案是从控制器显式返回 json,例如:

    @RequestMapping(value = "/listnowrapper")
public @ResponseBody String listNoWrapper() throws Exception{       
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(Arrays.asList("one","two","three","four","five")); 
}
于 2011-01-25T02:08:20.843 回答
5

我遇到和你一样的问题。

在我的方法声明中的列表前面添加@ResponseBody 后,问题就解决了。

例如:

public @ResponseBody List<MyObject> getObject

于 2011-02-15T06:35:23.757 回答
1

您可以编写自定义序列化程序:

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;
    }
}

它将忽略深度低于指定深度的外部对象(默认为 2)。

然后按如下方式使用它:

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));
    }
}

笔记:

  • 它只需要单个字段包装器,并且会在类似的东西上生成无效的 JSON{{field1: {...}, field2: {...}}
  • 如果您使用自定义SerializerFactory,您可能需要将其传递给序列化程序。
  • 它使用单独的序列化程序缓存,因此这也可能是一个问题。
于 2011-01-24T11:01:43.927 回答
0

老实说,我不会太快尝试解决这个问题,因为使用包装器确实会导致您的代码更具可扩展性。如果您将来扩展它以返回其他对象,则使用此 Web 服务的客户很可能不需要更改实现。

但是,如果所有客户端都期望一个未命名的数组,那么将来在该数组之外添加更多属性可能会破坏统一接口。

话虽如此,每个人都有他们想要以某种方式做某事的理由。您正在序列化的对象是什么样的?您是在序列化一个包含数组的对象,还是在序列化实际的数组本身?如果您的 POJO 包含一个数组,那么解决方案可能是将数组从 POJO 中拉出并序列化该数组。

于 2011-01-24T09:38:40.193 回答
0

我在尝试解决同样的问题时偶然发现了这个问题,但没有将它与 @ResponseBody 方法一起使用,但在我的序列化 JSON 中仍然遇到“包装器”。我的解决方案是将 @JsonAnyGetter 添加到方法/字段,然后包装器将从 JSON 中消失。

显然这是一个已知的杰克逊错误/解决方法:http: //jira.codehaus.org/browse/JACKSON-765

于 2013-03-28T17:01:40.950 回答