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

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

问题描述

我需要以下面的具体方式配置杰克逊。



要求




  1. 注释字段只能被序列化:


    • 如果该字段是普通对象,请将其 id

    • 如果该字段是对象的集合,序列化 id


  2. 注释字段的属性名称不同:


    • 一个正常对象,添加_ id后缀到属性名称

    • 如果该字段是对象的集合,请添加_ ids属性名称的后缀


  3. 对于注释,我正在想像一个自定义 @JsonId ,理想情况下,使用可选的值覆盖名称,就像 @JsonProperty

  4. id属性应由用户定义,可以使用:


    • 已经存在g Jackson的 @JsonIdentityInfo

    • 或创建另一个类或字段注释

    • 或通过决定哪些注释检查 id 属性可发现性(例如对于JPA方案有用)


  5. 应该使用包装的根值序列化对象

  6. 骆驼案例命名应转换为小写,带下划线

  7. 所有这些应该是反序列化(通过构建一个仅设置ID的实例)



一个例子



考虑这些POJO:

  //通知Jackson哪个属性是id 
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property =id

public abstract class BaseResource {
protected Long id;

// getter和setter
}

public class Resource extends BaseResource {
private String name;
@JsonId
private SubResource subResource;
@JsonId
private List< SubResource>子资源;

// getter和setter
}

public class SubResource extends BaseResource {
private String value;

// getter和setter
}

可能的序列化一个资源实例可以是:

  {
资源:{
id:1,
name:bla,
sub_resource_id:2,
sub_resource_ids:[
1 ,
2,
3
]
}
}



到目前为止...




  • 需求#5 可以通过以下列方式配置 ObjectMapper

      objectMapper.configure(DeserializationFeature。 UNWRAP_ROOT_VALUE,true); 
    objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);然后使用 @JsonRootName(example_root_name_here)

  • 需求#6 可以通过配置 ObjectMapper 以下方式:

      objectMapper.setPropertyNamingStrategy(
    PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);




正如你所看到的还有很多满足要求对于那些想知道为什么我需要这样的配置,这是因为我正在为 ember.js 开发一个REST webservice Ember Data)。
如果您可以帮助任何要求,您将非常感激。



谢谢!

解决方案

可以通过使用上下文序列化程序来完成大部分(全部)您的要求。从 ContextualDeserializer获取一个答案,将JSON映射到不同的类型的地图与杰克逊和杰克逊的维基( 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 //重要,以便它会得到包括!
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
扩展JsonSerializer< BaseResource>
实现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属性)
throws JsonMappingException
{
//首先查找用于getter或field的注释:
System.out.println(Finding annotations for+属性);

if(null == property){
return new ContextualJsonIdSerializer(mapper,false);
}

JsonId ann = property.getAnnotation(JsonId.class);
if(ann == null){//但是如果缺少,则从$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $%
}
if(ann == null){// || ann.length()== 0){
return this; // new ContextualJsonIdSerializer(false);
}
返回新的ContextualJsonIdSerializer(mapper,true);
}
}

此类查看 BaseResource 属性,并检查它们以查看是否存在 @JsonId 注释。如果只有Id属性被使用,否则在 ObjectMapper 中传递的值用于序列化值。这很重要,因为如果您尝试使用(基本上)在 ContextualSerializer 的上下文中的映射器,那么您将获得一个堆栈溢出,因为它将最终调用这些方法并且结束。



您的资源应该如下所示。我使用 @JsonProperty 注释,而不是将功能包装在 ContextualSerializer 中,因为它似乎很愚蠢地重塑轮子。 / p>

  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>子资源;

// getter和setter
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 并在原始的$ $ c $中注册一个模块c> ObjectMapper

  //创建原始的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);

//创建原始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);

//创建引用上下文序列化程序的模块
SimpleModule module = new SimpleModule(JsonId,new Version(1,0,0,null));
//所有对SubResource的引用都应该通过这个串行器运行
module.addSerializer(SubResource.class,new ContextualJsonIdSerializer(objectMapper2));
objectMapper.registerModule(module);

//现在只需使用原始的objectMapper序列化


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

Requirements

  1. Annotated fields are serialized with only their id:
    • If the field is a normal object, serialize its id
    • If the field is a collection of objects, serialize an array of id
  2. Annotated fields get their property names serialized differently:
    • If the field is a normal object, add "_id" suffix to property name
    • If the field is a collection of objects, add "_ids" suffix to property name
  3. For the annotation I was thinking something like a custom @JsonId, ideally with an optional value to override the name just like @JsonProperty does
  4. The id property should be defined by the user, either using:
    • 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)
  5. Objects should be serialized with a wrapped root value
  6. Camel case naming should be converted to lower case with underscores
  7. All of this should be deserializable (by constructing an instance with just the id setted)

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
}

A possible serialization of a Resource instance could be:

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

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);
    

    And then using @JsonRootName("example_root_name_here") in my POJO's.

  • Requirement #6 can be accomplished by configuring ObjectMapper in the following way:

    objectMapper.setPropertyNamingStrategy(
        PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    

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.

Thanks!

解决方案

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.

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 {
}

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);
    }
}

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.

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;}
}

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

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

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