如何(De)使用Jackson基于注释从对象序列化字段?

我需要以特定的方式配置Jackson,我将在下面介绍。

要求

  1. 带注释的字段仅使用其ID进行序列化:
    • 如果该字段是普通对象,则序列化其id
    • 如果该字段是对象的集合,则序列化一个id数组
  2. 带注释的字段以不同方式序列化其属性名称:
    • 如果该字段是普通对象,请将"_id"后缀添加到属性名称
    • 如果该字段是对象的集合,请将"_ids"后缀添加到属性名称
  3. 对于注释,我正在考虑像自定义的@JsonId ,理想情况下使用可选值来覆盖名称,就像@JsonProperty一样
  4. id属性应由用户定义,使用:
    • 已经存在的Jackson的@JsonIdentityInfo
    • 或者通过创建另一个类或字段注释
    • 或者通过决定检查id属性可发现性的注释(例如,对JPA场景有用)
  5. 应使用包装的根值序列化对象
  6. Camel case命名应该用下划线转换为小写
  7. 所有这些都应该是可反序列化的(通过构造一个只设置了id的实例)

一个例子

考虑到这些POJO:

 //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 subResources; //getters and setters } public class SubResource extends BaseResource{ private String value; //getters and setters } 

Resource实例的可能序列化可能是:

 { "resource":{ "id": 1, "name": "bla", "sub_resource_id": 2, "sub_resource_ids": [ 1, 2, 3 ] } } 

至今…

  • 可以通过以下方式配置ObjectMapper来完成要求#5

     objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true); 

    然后在我的POJO中使用@JsonRootName("example_root_name_here")

  • 可以通过以下方式配置ObjectMapper来完成要求#6

     objectMapper.setPropertyNamingStrategy( PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); 

如您所见,仍有许多要求可以实现。 对于那些想知道为什么我需要这样的配置的人来说,这是因为我正在为ember.js (更具体地说是Ember Data)开发一个REST web服务。 如果您可以帮助满足任何要求,您将非常感激。

谢谢!

大多数(全部?)您的要求可以通过使用上下文序列化器来完成。 从ContextualDeserializer获得一个答案, 用于将JSON映射到Jackson和Jackson的wiki( http://wiki.fasterxml.com/JacksonFeatureContextualHandlers )的不同类型的地图,我能够得到以下内容。

您需要从@JsonId注释开始,这是指示属性只需要使用Id属性的键。

 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,它可以完成繁重的工作。

 import com.fasterxml.jackson.databind.ser.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.core.*; import java.io.*; public class ContextualJsonIdSerializer extends JsonSerializer implements ContextualSerializer/**/ { 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 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的上下文中的映射器,那么您将获得堆栈溢出,因为它最终会反复调用这些方法。

您的资源应该类似于以下内容。 我使用@JsonProperty注释而不是将function包装在ContextualSerializer因为重新发明轮子似乎很愚蠢。

 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 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 getSubResources() {return subResources;} public void setSubResources(List subResources) {this.subResources = subResources;} } 

最后,执行序列化的方法只是创建一个额外的ObjectMapper并在原始ObjectMapper注册一个模块。

 // 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 
Interesting Posts