java使用Jackson在运行时将实体动态序列化为其ID或完整表示形式
我们正在使用JavaEE7(RESTEasy/Hibernate/Jackson)开发一个RESTful API
我们希望API在默认情况下使用其ID序列化所有子实体。我们这样做主要是为了保持与反序列化策略的一致性,在反序列化策略中,我们坚持接收ID
然而,我们也希望我们的用户能够选择通过自定义端点或查询参数(未确定)获得任何子实体的扩展视图。例如:
// http://localhost:8080/rest/operator/1
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=organization
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": 23
}
// http://localhost:8080/rest/operator/1?expand=enduser
// =====================================
{
"operatorId": 1,
"organization": 34,
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
// http://localhost:8080/rest/operator/1?expand=organization,enduser
// =====================================
{
"operatorId": 1,
"organization": {
"organizationId": 34,
"organizationName": "name"
},
"endUser": {
"endUserId": 23,
"endUserName": "other name"
}
}
是否有一种方法可以动态更改Jackson的行为,以确定指定的AbstractEntity字段是以完整形式序列化还是作为其ID序列化?怎么做呢
其他信息
我们知道使用子实体的ID序列化子实体的几种方法,包括:
public class Operator extends AbstractEntity {
...
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="organizationId")
@JsonIdentityReference(alwaysAsId=true)
public getOrganization() { ... }
...
}
及
public class Operator extends AbstractEntity {
...
@JsonSerialize(using=AbstractEntityIdSerializer.class)
public getOrganization() { ... }
...
}
其中AbstractEntityIdSerializer使用实体的ID序列化实体
问题是,我们不知道用户如何覆盖默认行为并恢复到标准的Jackson对象序列化。理想情况下,他们还可以选择以完整形式序列化哪些子属性
如果可能的话,可以在运行时为任何属性动态切换@jsonidentialreference的alwaysAsId参数,或者对ObjectMapper/ObjectWriter进行等效更改
更新:正在工作(?)解决方案 我们还没有机会完全测试这一点,但我一直在研究一个利用重写Jackson的AnnotationIntroSector类的解决方案。它似乎在按预期工作
public class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
private final Set<String> expandFieldNames_;
public CustomAnnotationIntrospector(Set<String> expandFieldNames) {
expandFieldNames_ = expandFieldNames;
}
@Override
public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectIdInfo) {
JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class);
if (ref != null) {
for (String expandFieldName : expandFieldNames_) {
String expandFieldGetterName = "get" + expandFieldName;
String propertyName = ann.getName();
boolean fieldNameMatches = expandFieldName.equalsIgnoreCase(propertyName);
boolean fieldGetterNameMatches = expandFieldGetterName.equalsIgnoreCase(propertyName);
if (fieldNameMatches || fieldGetterNameMatches) {
return objectIdInfo.withAlwaysAsId(false);
}
}
objectIdInfo = objectIdInfo.withAlwaysAsId(ref.alwaysAsId());
}
return objectIdInfo;
}
}
在序列化时,我们复制ObjectMapper(这样AnnotationIntrospector会再次运行),并按如下方式应用CustomAnnotationIntrospector:
@Context
private HttpRequest httpRequest_;
@Override
writeTo(...) {
// Get our application's ObjectMapper.
ContextResolver<ObjectMapper> objectMapperResolver = provider_.getContextResolver(ObjectMapper.class,
MediaType.WILDCARD_TYPE);
ObjectMapper objectMapper = objectMapperResolver.getContext(Object.class);
// Get Set of fields to be expanded (pre-parsed).
Set<String> fieldNames = (Set<String>)httpRequest_.getAttribute("ExpandFields");
if (!fieldNames.isEmpty()) {
// Pass expand fields to AnnotationIntrospector.
AnnotationIntrospector expansionAnnotationIntrospector = new CustomAnnotationIntrospector(fieldNames);
// Replace ObjectMapper with copy of ObjectMapper and apply custom AnnotationIntrospector.
objectMapper = objectMapper.copy();
objectMapper.setAnnotationIntrospector(expansionAnnotationIntrospector);
}
ObjectWriter objectWriter = objectMapper.writer();
objectWriter.writeValue(...);
}
这种方法有什么明显的缺陷吗?它似乎相对简单,而且是完全动态的
# 1 楼答案
答案是Jackson's mixin feature:
创建一个简单的Java类,它的方法签名与实体的指定方法完全相同。用修改后的值对该方法进行注释。方法的主体无关紧要(不会被调用):
您可以使用Jackson的模块系统将mixin绑定到要序列化的实体:这可以在运行时决定
现在,映射器将从mixin中获取注释,但调用实体的方法
# 2 楼答案
如果您正在寻找一个需要扩展到所有资源的通用解决方案,您可以尝试以下方法。我试着用Jersey和Jackson来解决以下问题。它还应该与RestEasy一起使用
基本上,您需要编写一个定制的jackson提供程序,为
expand
字段设置一个特殊的序列化程序。此外,还需要将扩展字段传递给序列化程序,以便决定如何对扩展字段进行序列化ExpandField。爪哇
这是本地的。爪哇
ExpandFieldSerializer。爪哇
接线员。爪哇
最后一步是注册新的
ExpandFieldJacksonProvider
。在Jersey,我们通过一个javax.ws.rs.core.Application
实例注册它,如下所示。我希望RestEasy中也有类似的东西。默认情况下,大多数JAX-RS库倾向于通过自动发现加载默认JacksonJaxbJsonProvider
。您必须确保已禁用Jackson的自动发现功能,并且已注册新的ExpandFieldJacksonProvider