Jackson with Spring boot:控制对象替换特定方法
Posted
技术标签:
【中文标题】Jackson with Spring boot:控制对象替换特定方法【英文标题】:Jackson with Spring boot: control object substitution for specific method 【发布时间】:2018-09-23 07:55:13 【问题描述】:我将实体类型作为递归树中的项,因此任何项都引用其父项和子项(相同类型)
public class Category
private Integer id;
private String displayName;
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
@JsonIdentityReference(alwaysAsId=true)
private Category parent;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
@JsonIdentityReference(alwaysAsId=true)
private Set<Category> children;
// constructors, getters and setters
如您所见,我用@JsonIdentityReference
注释标记了两个引用字段,强制它们呈现为普通ID。目前使用此设置,实体呈现如下:
// from .../categories/0
"id" : 0,
"displayName" : "Root",
"parent" : null,
"children" : [ 1, 13, 17 ]
(这很好)。
然而,客户端非常常见的使用场景是从特定节点开始获取整个子树,在此实现中需要向服务器发出多个请求。我想创建另一个 enpoint,允许客户端通过单个请求执行此操作。
@RestController
@RequestMapping(value = "/categories")
public class CategoryController
@Autowired
private CategoryService categoryService;
@Autowired
private CategoryRepository categoryRepo;
@RequestMapping(value = "/tree", method = RequestMethod.GET)
public Category getTree(@RequestParam(name = "root", required = false) Integer id)
Category root = id == null ? categoryService.getRoot() : categoryRepo.findOne(id);
return categoryService.getTree(root);
// other endpoints
// getOne(id)
// getAll()
仅当我从 children
字段中手动删除 (alwaysAsId=true)
标志时,来自此端点的响应才会呈现完整对象。但是,我希望两个端点共存并呈现不同的布局。所以,问题是:如何让特定的控制器方法选择是否用 id 替换完整的实体?。
我已经尝试过与@JsonView
的各种组合,但似乎这种方法不起作用。 @JsonView
只能控制是否包含或完全省略特定字段,而我需要 children
字段仅更改布局。另外,由于子类型与实体类型相同,因此无法在不同类之间拆分注释标记。
【问题讨论】:
我知道我已经提交了一个关于此的功能请求,但我找不到它。 【参考方案1】:/* 更新了答案,在关于失去多层次层次结构中的视图标记的评论之后。 */
您仍然可以通过使用@JsonView
s 和在Category
对象内的小变通方法来实现您正在寻找的结果。
假设我们有两种类型用作标记来输出 JSON 视图 FlatView
和 HierarchicalView
。
解决的原理是:
我们将大部分字段与视图相关联。 children
字段仅与 FlatView
关联。
我们为children
字段创建了一个额外的getter,为其提供了另一个属性名称,不会与该字段或其原始getter 发生冲突。此属性仅与 HierarchicalView
关联。
它为Category
类提供以下布局:
public class Category
@JsonView( FlatView.class, HierarchicalView.class) // both views
private Integer id;
@JsonView( FlatView.class, HierarchicalView.class) // both views
private String displayName;
@JsonView( FlatView.class, HierarchicalView.class) // both views
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
private Category parent;
@JsonView(FlatView.class) // flat view only!
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
private Set<Category> children;
@JsonView(HierarchicalView.class) // hierarchical view only!
// note that the name is not `getChildren`: this generates another JSON property.
// Or use @JsonProperty to customize it.
public Set<Category> getChildrenAsTree()
return this.children;
// constructors, getters and setters
产生输出的控制器方法应该用各自的@JsonView
s 注释。
缺点:
此方法不会对以不同方式表示的相同信息使用相同的属性名称。这可能会给客户端的属性匹配、反序列化带来一些困难。
如果Category
在某处仍使用默认视图,它将包含通过附加getter 访问的字段和属性。
冗余和重复的注释 :) 更难维护。
children
字段的冗余 getter。
【讨论】:
我尝试了这个想法,但它有一个严重的缺点 - 当jsonGenerator.writeObject()
被调用时,上下文丢失并且构造了新的SerializerProvider
不知道活动视图。结果,只有树的第一层被渲染,更深的层落入另一个分支。应该明确使用DefaultSerializerProvider
(不知何故),我仍在尝试找出确切的方法。
好话,它让我重新思考了这种方法。我已经更新了答案。【参考方案2】:
我终于找到了解决办法。它基于 Antot 答案的第一个版本,并为 children
集合的 content 使用自定义序列化程序。生成树的端点用@JsonView(Category.TreeView.class)
注释标记,未标记的字段包含打开
#application.properties
spring.jackson.mapper.default-view-inclusion=true
// Category.java
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonSerialize(contentUsing = CategoryChildSerializer.class)
private Set<Category> children;
// CategoryChildSerializer.java
public class CategoryChildSerializer extends JsonSerializer<Category> implements ResolvableSerializer
private JsonSerializer<Object> defaultSerializer;
private JsonSerializer<Object> idSerializer;
public void serialize(Category value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException
if(provider.getActiveView() == Category.TreeView.class)
defaultSerializer.serialize(value, gen, provider);
else
idSerializer.serialize(value.getId(), gen, provider);
public void resolve(SerializerProvider provider) throws JsonMappingException
defaultSerializer = provider.findValueSerializer(Category.class);
idSerializer = provider.findValueSerializer(Integer.class);
注意切换序列化器如何委托序列化而不是直接使用JsonGenerator
。实现ResolvableSerializer
实际上是可选的,它只是优化步骤。
【讨论】:
以上是关于Jackson with Spring boot:控制对象替换特定方法的主要内容,如果未能解决你的问题,请参考以下文章
Jackson 在我的 Spring Boot 应用程序中忽略了 spring.jackson.properties
如何在 Spring Boot 1.4 中自定义 Jackson
Spring Boot没有使用Jackson Kotlin插件