将自引用对象列表序列化为不带方括号的 JSON

Posted

技术标签:

【中文标题】将自引用对象列表序列化为不带方括号的 JSON【英文标题】:Serialize self referenced object list to JSON without square brackets 【发布时间】:2021-11-02 08:52:44 【问题描述】:

我正在编写一个spring-boot 应用程序,我的控制器应该返回JSON

我的模特:

public class Category 
    private String name;
    private List<Category> subCategory;
    //getters, setters, constructors 

为此模型填充的测试/数据:

    @Test
    public void testCategory() throws JsonProcessingException 
        Category city1 = new Category("London",null);
        Category city2 = new Category("Leeds",null);
        Category country = new Category("UK",Arrays.asList(city1,city2));
        Category continent = new Category("Europe", Arrays.asList(country));
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        System.out.println( mapper.writeValueAsString(continent));
    

这会产生如下输出:


  "name" : "Europe",
  "subCategory" : [ 
    "name" : "UK",
    "subCategory" : [ 
      "name" : "London",
      "subCategory" : null
    , 
      "name" : "Leeds",
      "subCategory" : null
     ]
   ]

期望的输出(忽略格式,除了每个子类别应该缩进):

   
     "Europe": 
          "UK":  
              "London": ,
               "Leeds":  
              
            
    

这是我原来的问题。 我尝试了一些东西,例如Map&lt;String,Object&gt;subcategory,它可以工作,但它会产生输出,其中LondonLeeds 被包裹在[] 中。我不能让那些回复客户。

编辑:

如果我使用基于地图的方法:

    @Test
    public void testCategory2() throws JsonProcessingException 
        Map<String,Object> leeds = Map.of("Leeds",new HashMap<>());
        Map<String,Object> london = Map.of("London",new HashMap<>());
        MultiValuedMap<String, Object> uk = new        HashSetValuedHashMap<>();
        uk.put("UK",london);
        uk.put("UK",leeds);
        Map<String,Object> europe = Map.of("Europe", uk.asMap());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        String json = mapper.writeValueAsString(europe);
        System.out.println(json);
    

输出:


  "Europe" : 
    "UK" : [ 
      "London" :  
    , 
      "Leeds" :  
     ]
  

【问题讨论】:

您想要的输出不是有效的 JSON。也许您忘记了开头的 和结尾的 ?如果您的客户真的期望某些不是有效的 JSON,您将无法使用 Jackson 来满足它。 @neofelis,谢谢,是的,我失踪了。编辑以反映正确的 Json。 【参考方案1】:

所以我不是“一堆地图”方法的忠实拥护者——我不喜欢它以至于不能投反对票,但它......不是最美观的代码。您正在按摩您的价值观,直到杰克逊根据其默认设置对它们进行您想要的处理。你可以告诉杰克逊如何处理你的价值观。这需要更多的打字,但我发现它更优雅;可能,它也更易于理解/可扩展。

首先,请注意 - 您的问题和基于地图的答案都会使用 new 创建一个 ObjectMapper。如果您使用的是 spring-starter-json (或包含它的类似 spring-starter-web 的东西),Spring Boot 会为您提供一个对象映射器。你可以自动接线。在这个答案中,我假设你想这样做,因为我不明白你为什么不这样做。

解决问题。你想把一个 Java 列表变成一个对象;杰克逊默认不会这样做。一般来说,如果你想让 Jackson 做一些默认情况下不能做的事情,你可以编写一个自定义(反)序列化器。在您的情况下,我们需要一个用于Categorys 的自定义序列化程序,并且我们需要一个用于Categorys 的列表 的自定义序列化程序。 (如果您愿意在序列化它们之前将Categorys 包装在单例列表中,那么您可以只使用第二个,但我认为我不想这样做。)所以让我们编写这些序列化程序。它们都很容易编写。

public class ListOfCategorySerializer extends StdSerializer<List<Category>> 
    public ListOfCategorySerializer(JavaType t) 
        super(t);
    

    @Override
    public void serialize(List<Category> value, JsonGenerator gen, SerializerProvider provider)
            throws IOException 
        gen.writeStartObject();

        for (var c: value) 
            gen.writeFieldName(c.getName());
            gen.writeObject(c.getCategories());
        

        gen.writeEndObject();
    

public class CategorySerializer extends StdSerializer<Category> 
    public CategorySerializer(Class<Category> c)  super(c); 

    @Override
    public void serialize(Category value, JsonGenerator gen, SerializerProvider provider)
            throws IOException 
        gen.writeStartObject();

        gen.writeFieldName(value.getName());

        // when we're done configuring stuff, the line below will result in
        // Jackson using our ListOfCategorySerializer
        provider.defaultSerializeValue(value.getCategories(), gen);

        gen.writeEndObject();
    

然后,我们需要告诉杰克逊使用它们。通常,您只需在实体类或其字段上使用@JsonSerialize 就可以做到这一点,但在这里您不能这样做,因为您需要对类使用一个序列化程序,而对类的一个字段使用另一个序列化程序。所以你需要写一个模块。这里的好处是您使用的是 Spring Boot,它会自动使用它提供给您的 ObjectMapper 注册所有 Module bean。

@Component
public class CategoriesModule extends SimpleModule 
    @Override
    public void setupModule(SetupContext context) 
        JavaType t = context.getTypeFactory().constructCollectionLikeType(List.class, Category.class);
        ListOfCategorySerializer ser = new ListOfCategorySerializer(t);
        this.addSerializer(ser);

        this.addSerializer(new CategorySerializer(Category.class));

        super.setupModule(context);
    

现在我们可以在一个有 @Autowired ObjectMapper mapper 的类中说

var london = new Category("London", Arrays.asList(new Category[] ));
var leeds = new Category("Leeds", Arrays.asList(new Category[] ));
var uk = new Category("UK", Arrays.asList(new Category[] london, leeds));
var europe = new Category("Europe", Arrays.asList(new Category[] uk));

var s = mapper.writeValueAsString(europe);

s 将包含您预期的输出。嗯,几乎完全一样;它不会包含任何类型的填充。你知道一种获取填充的方法——使用ObjectMapper.enable——但由于你使用的是Spring Boot,更好的方法是将spring.jackson.serialization.indent-output=true放入你的application.properties

【讨论】:

哇,谢谢!我喜欢这个。基于地图的方法虽然有效,但还是有问题的。更清洁和可维护的方法。谢谢。【参考方案2】:

这将产生你想要的结果

  @Test
    public void testCategory2() throws JsonProcessingException 
        Map<String,Object> ukNestedField = new HashMap<>();
        ukNestedField.put("Leeds", new HashMap<String, Object>());
        ukNestedField.put("London", new HashMap<String, Object>());

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

        Map<String, Map<String, Object>> outer = new HashMap<>();
        outer.put("UK",ukNestedField );
        outer.put("FR",franceNestedField );

        Map<String,Object> europe = Map.of("Europe", outer.asMap());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        String json = mapper.writeValueAsString(europe);
        System.out.println(json);
    

【讨论】:

谢谢,但我很确定它可以做到,即使像我这样使用动态字段。 @IanMcGrath 肯定可以通过手动序列化来完成,我想说的是它没有任何意义。 肯定不能使用静态定义的 Java 对象来完成。您必须动态创建 json 响应。检查如何动态地做到这一点baeldung.com/jackson-json-node-tree-model 是的,它没有。不幸的是,客户希望它是这样的。在过去的几个小时里,我一直在为此烦恼。知道我应该如何使用/不使用手动序列化。如果有帮助,我可以分享我使用基于地图的方法的代码。 是的,必须是动态的。是的,我希望其他国家..

以上是关于将自引用对象列表序列化为不带方括号的 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Newton JSON 如何将对象序列化为空括号?

JSON 对象在使用 JsonResult 返回时被序列化为空括号

需要帮助阅读不在括号 [] 内的 Json 对象列表

将实体框架对象序列化为 JSON

将对象列表序列化为没有父对象名称的 JSON

将特定 JSON 字段反序列化为 Unity 中的对象列表