如何使用Jackson基于注释从对象中(反)序列化字段? [英] How to (De)serialize field from object based on annotation using Jackson?

查看:39
本文介绍了如何使用Jackson基于注释从对象中(反)序列化字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要以我将在下面描述的特定方式配置 Jackson.

I need to configure Jackson in a specific way which I'll describe below.

  1. 带注释的字段仅使用其 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 in ObjectMapper is used to serialize the value. This is important because if you try to use the mapper that is (basically) in the context of the ContextualSerializer 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 the ContextualSerializer 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 original 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
      

      这篇关于如何使用Jackson基于注释从对象中(反)序列化字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆