【问题标题】:Suppress wrapper object when serializing Java object into JSON using Jackson使用 Jackson 将 Java 对象序列化为 JSON 时抑制包装器对象
【发布时间】:2011-06-13 12:40:15
【问题描述】:

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

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

当我真正想要的是这样的时候:

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

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

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

解决方案的限制条件

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

我的尝试

  • 我尝试将 Java POJO 列表替换为 Java POJO 数组,结果相同。
  • 我尝试@JsonTypeInfo(use = Id.NONE) 认为这可能会抑制包装器,但它没有。

【问题讨论】:

  • 这听起来像是你序列化了错误的对象。为什么不能直接用Mapper序列化列表而不是序列化周围的POJO呢?
  • 我直接序列化列表。 POJO 在列表中。问题是当我序列化列表时,杰克逊在它周围添加了一个包装器。我在想可能是杰克逊本身将列表对象与其支持数组区分开来,所以我什至尝试直接序列化一个数组。没有运气; Jackson 仍然添加了包装对象。
  • 你能告诉我们代码吗?我经常使用杰克逊,从来没有经历过你所描述的。
  • 嘿杰森。请参阅下面我对 Biju 的回复。
  • 查看我对自己问题的回答:link

标签: java json spring jackson


【解决方案1】:

在我运行时处于测试模式:

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

【讨论】:

  • 这正是我所暗示的。序列化数组而不是包含数组的对象。 +1
  • 谢谢大家。这一定是怎么回事。我确实在使用 MappingJacksonJsonView,我只是假设杰克逊是这里的违规玩家。 :-) 感谢您的帮助。
【解决方案2】:

我遇到了和你一样的问题。

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

例如:

public @ResponseBody List<MyObject> getObject

【讨论】:

    【解决方案3】:

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

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

    注意事项:

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

    【讨论】:

    • 好的,我来看看。我希望有一些更简单的方法——自定义序列化程序方法似乎比手动生成 JSON 更有效。 (尽管我们可以在实施更多映射时分摊成本。)
    【解决方案4】:

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

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

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

    【讨论】:

    • 是的,我的理由是合同没有被抢购一空——客户端组件需要一个原始数组。我正在序列化数组本身。 Jackson 烦人地添加了包装器。
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-12-23
      • 1970-01-01
      • 2019-08-21
      • 1970-01-01
      • 1970-01-01
      • 2016-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多