如何使用Jackson基于注释从对象中(反)序列化字段? [英] How to (De)serialize field from object based on annotation using Jackson?
问题描述
我需要以我将在下面描述的特定方式配置 Jackson.
I need to configure Jackson in a specific way which I'll describe below.
- 带注释的字段仅使用其 ID 进行序列化:
- 如果字段是普通对象,序列化它的
id
- 如果字段是对象的集合,序列化一个
id
的数组
- 如果字段是普通对象,序列化它的
- 如果字段为普通对象,则在属性名称后添加
"_id"
后缀 - 如果字段是对象的集合,则在属性名称后添加
"_ids"
后缀
- 已经存在的Jackson的
@JsonIdentityInfo
- 或者通过创建另一个类或字段注释
- 或者通过决定检查哪个注解来检查
id
属性的可发现性(例如,对于 JPA 场景很有用)
- The already existing Jackson's
@JsonIdentityInfo
- Or by creating another class or field annotation
- Or by deciding which annotation to inspect for
id
property discoverability (useful for JPA scenarios, for example)
一个例子
考虑到这些 POJO:
An example
Considering these POJO's:
//Inform Jackson which property is the id
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public abstract class BaseResource{
protected Long id;
//getters and setters
}
public class Resource extends BaseResource{
private String name;
@JsonId
private SubResource subResource;
@JsonId
private List<SubResource> subResources;
//getters and setters
}
public class SubResource extends BaseResource{
private String value;
//getters and setters
}
Resource
实例的可能序列化可能是:
A possible serialization of a Resource
instance could be:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
到目前为止...
需求#5可以通过以下方式配置
ObjectMapper
来实现:So far...
Requirement #5 can be accomplished by configuring
ObjectMapper
in the following way:objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
然后在我的 POJO 中使用
@JsonRootName("example_root_name_here")
.And then using
@JsonRootName("example_root_name_here")
in my POJO's.需求#6可以通过以下方式配置
ObjectMapper
来实现:Requirement #6 can be accomplished by configuring
ObjectMapper
in the following way:objectMapper.setPropertyNamingStrategy( PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
如您所见,仍有许多要求需要满足.对于那些想知道为什么我需要这样的配置的人来说,这是因为我正在为 ember.js(更具体地说余烬数据).如果您能帮助满足任何要求,将不胜感激.
As you can see there are still lots of requirements to fulfill. For those wondering why I need such a configuration, it's because I'm developing a REST webservice for ember.js (more specifically Ember Data). You would appreciate very much if you could help with any of the requirements.
谢谢!
推荐答案
您的大部分(全部?)需求都可以通过使用上下文序列化程序来实现.从 ContextualDeserializer 中获取一个答案,用于将 JSON 映射到不同的Jackson 的地图类型 和 Jackson 的 wiki (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) 我能够想出以下内容.
Most (all?) of your requirements can be accomplished through the use of a contextual serializer. Taking one answer from ContextualDeserializer for mapping JSON to different types of maps with Jackson and Jackson's wiki (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) I was able to come up with the following.
你需要从@JsonId注解开始,它是指示一个属性只需要使用Id属性的关键.
You need to start with the @JsonId annotation, which is the key indicating a property needs to only use the Id property.
import com.fasterxml.jackson.annotation.*; import java.lang.annotation.*; @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation // important so that it will get included! public @interface JsonId { }
接下来是实际的 ContextualSerializer,它负责繁重的工作.
Next is the actual ContextualSerializer, which does the heavy lifting.
import com.fasterxml.jackson.databind.ser.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.core.*; import java.io.*; public class ContextualJsonIdSerializer extends JsonSerializer<BaseResource> implements ContextualSerializer/*<BaseResource>*/ { private ObjectMapper mapper; private boolean useJsonId; public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); } public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) { this.mapper = mapper; this.useJsonId = useJsonId; } @Override public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException { if ( useJsonId ) { jgen.writeString(br.getId().toString()); } else { mapper.writeValue(jgen, br); } } @Override public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property) throws JsonMappingException { // First find annotation used for getter or field: System.out.println("Finding annotations for "+property); if ( null == property ) { return new ContextualJsonIdSerializer(mapper, false); } JsonId ann = property.getAnnotation(JsonId.class); if (ann == null) { // but if missing, default one from class ann = property.getContextAnnotation(JsonId.class); } if (ann == null ) {//|| ann.length() == 0) { return this;//new ContextualJsonIdSerializer(false); } return new ContextualJsonIdSerializer(mapper, true); } }
该类查看
BaseResource
属性并检查它们以查看是否存在@JsonId
注释.如果是,则仅使用 Id 属性,否则使用传入的ObjectMapper
来序列化该值.这很重要,因为如果您尝试使用(基本上)在ContextualSerializer
上下文中的映射器,那么您将获得堆栈溢出,因为它最终会一遍又一遍地调用这些方法.This class looks at
BaseResource
properties and inspects them to see if the@JsonId
annotation is present. If it is then only the Id property is used, otherwise a passed inObjectMapper
is used to serialize the value. This is important because if you try to use the mapper that is (basically) in the context of theContextualSerializer
then you will get a stack overflow since it will eventually call these methods over and over.您的资源应如下所示.我使用了
@JsonProperty
注释,而不是将功能包装在ContextualSerializer
中,因为重新发明轮子似乎很愚蠢.You're resource should look something like the following. I used the
@JsonProperty
annotation instead of wrapping the functionality in theContextualSerializer
because it seemed silly to reinvent the wheel.import java.util.*; import com.fasterxml.jackson.annotation.*; public class Resource extends BaseResource{ private String name; @JsonProperty("sub_resource_id") @JsonId private SubResource subResource; @JsonProperty("sub_resource_ids") @JsonId private List<SubResource> subResources; //getters and setters public String getName() {return name;} public void setName(String name) {this.name = name;} public SubResource getSubResource() {return subResource;} public void setSubResource(SubResource subResource) {this.subResource = subResource;} public List<SubResource> getSubResources() {return subResources;} public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;} }
最后,执行序列化的方法只是创建了一个额外的
ObjectMapper
,并在原来的ObjectMapper
中注册了一个模块.Finally the method that performs the serialization just creates an additional
ObjectMapper
and registers a module in the originalObjectMapper
.// Create the original ObjectMapper ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true); objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); // Create a clone of the original ObjectMapper ObjectMapper objectMapper2 = new ObjectMapper(); objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true); objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); // Create a module that references the Contextual Serializer SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null)); // All references to SubResource should be run through this serializer module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2)); objectMapper.registerModule(module); // Now just use the original objectMapper to serialize
这篇关于如何使用Jackson基于注释从对象中(反)序列化字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!