将自引用对象列表序列化为不带方括号的 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<String,Object>
和subcategory
,它可以工作,但它会产生输出,其中London
和Leeds
被包裹在[]
中。我不能让那些回复客户。
编辑:
如果我使用基于地图的方法:
@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 做一些默认情况下不能做的事情,你可以编写一个自定义(反)序列化器。在您的情况下,我们需要一个用于Category
s 的自定义序列化程序,并且我们还需要一个用于Category
s 的列表 的自定义序列化程序。 (如果您愿意在序列化它们之前将Category
s 包装在单例列表中,那么您可以只使用第二个,但我认为我不想这样做。)所以让我们编写这些序列化程序。它们都很容易编写。
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的主要内容,如果未能解决你的问题,请参考以下文章