【问题标题】:How to (De)serialize field from object based on annotation using Jackson?如何使用 Jackson 基于注释从对象(反)序列化字段?
【发布时间】:2013-09-10 16:12:46
【问题描述】:

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

要求

  1. 带注释的字段仅使用其 ID 进行序列化:
    • 如果字段是普通对象,序列化其id
    • 如果字段是对象的集合,则序列化id的数组
  2. 带注释的字段以不同的方式序列化其属性名称:
    • 如果字段为普通对象,属性名后加"_id"后缀
    • 如果该字段是对象的集合,则在属性名称中添加"_ids"后缀
  3. 对于注释,我想的是自定义 @JsonId 之类的东西,最好有一个可选值来覆盖名称,就像 @JsonProperty 所做的那样
  4. id 属性应该由用户定义,或者使用:
    • 已经存在的杰克逊@JsonIdentityInfo
    • 或者通过创建另一个类或字段注释
    • 或者通过决定检查哪个注释来检查 id 属性的可发现性(例如,对 JPA 场景很有用)
  5. 对象应使用包装的根值进行序列化
  6. 驼峰式命名应转换为带下划线的小写
  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<SubResource> 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
        ]
    }
}

到目前为止...

  • 要求#5可以通过如下方式配置ObjectMapper来实现:

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

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

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

    objectMapper.setPropertyNamingStrategy(
        PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    

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

谢谢!

【问题讨论】:

  • 体验如何?我现在正面临这个要求。如果您能提供您的配置决定,我们将不胜感激。谢谢
  • 您是否考虑过创建自己的AnnotationIntrospector
  • 我还必须与 ember 数据进行交互。我已经看到较新的 Jackson 版本提供了缓解这种情况的功能。你的解决方案是什么? p.s.我正在查看已批准的答案以获得洞察力。
  • @ieugen 现在我会走 JSONApi 路线并尝试 kathasis:katharsis.io Ember Data 支持开箱即用的 JSONApi 标准。
  • 谢谢@miguelcobain。我在看khatarsis 。我正在使用 Vertx.io,希望我能够很好地集成它们。澄清一下:您能否确认 ember 数据与 khatarsis 集成开箱即用?

标签: java json ember.js jackson ember-data


【解决方案1】:

您的大部分(全部?)需求都可以通过使用上下文序列化程序来实现。从ContextualDeserializer for mapping JSON to different types of maps with 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<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 上下文中的映射器,那么您将得到堆栈溢出,因为它最终会一遍又一遍地调用这些方法。

您的资源应如下所示。我使用@JsonProperty 注释而不是在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<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 中注册一个模块。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    • 2020-04-11
    • 1970-01-01
    • 2012-05-01
    • 2020-08-07
    相关资源
    最近更新 更多