如何使用尊重自定义注释的Jackson来执行自定义JSON反序列化?

问题描述

我正在尝试将JSON反序列化为无法修改的自定义POJO。该POJO具有来自我无法使用的不同自定义内部序列化框架的注释。如何创建将遵循这些注释的自定义反序列化器?

以下是POJO示例:

public class ExampleClass {
    @Property(name = "id")
    public String id;

    @Property(name = "time_windows")
    @NotNull
    public List<TimeWindow> timeWindows = new ArrayList<>();

    public static class TimeWindow {
        @Property(name = "start")
        public Long start;

        @Property(name = "end")
        public Long end;
    }
}

因此,在这种情况下,解串器将在JSON中查找与Property批注对应的字段,并使用该批注中的值来确定要捕获的字段。如果属性没有具有Property批注,则应将其忽略。

我一直在浏览Jackson的文档,但无法确切找到我需要的东西。这是AnnotationIntrospector有用的地方吗?还是ContextualDeserializer

任何朝着正确方向的指针将不胜感激!


更新:我尝试在评论中实施建议,但没有成功。

这是我对内省者的最初实现:

class CustomAnnotationInspector : JacksonAnnotationIntrospector () {
    override fun hasIgnoreMarker(m: AnnotatedMember?): Boolean {
        val property = m?.getAnnotation(Property::class.java)
        return property == null
    }

    override fun findNameForDeserialization(a: Annotated?): PropertyName {
        val property = a?.getAnnotation(Property::class.java)
        return if (property == null) {
            super.findNameForDeserialization(a)
        } else {
            PropertyName(property.name)
        }
    }
}

这是我实际使用的地方:

// Create an empty instance of the request object.
val paramInstance = nonPathParams?.type?.getDeclaredConstructor()?.newInstance()

// Create new object mapper that will write values from
// JSON into the empty object.
val mapper = ObjectMapper()

// Tells the mapper to respect custom annotations.
mapper.setAnnotationIntrospector(CustomAnnotationInspector())

// Write the contents of the request body into the StringWriter
// (this is required for the mapper.writeValue method
val sw = StringWriter()
sw.write(context.bodyAsString)

// Deserialize the contents of the StringWriter
// into the empty POJO.
mapper.writeValue(sw,paramInstance)

不幸的是,似乎从未调用过findNameForDeserialization,并且没有将JSON值写入paramInstance中。有人可以发现我要去哪里错吗?

谢谢!


更新2:我稍微更改了代码,现在可以识别属性名称,但杰克逊无法创建该对象的实例。

这是我的新代码:

val mapper = ObjectMapper()

// Tells the mapper to respect CoreNg annotations.
val introspector = CustomAnnotationInspector()
mapper.setAnnotationIntrospector(introspector)

val paramInstance = mapper.readValue(context.bodyAsString,nonPathParams?.type)

我在自定义批注自省内窥镜中的断点被击中。但我收到以下异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `app.employee.api.employee.BOUpsertEmployeeRequest` (no Creators,like default constructor,exist): cannot deserialize from Object value (no delegate- or property-based Creator)

这是我要反序列化的POJO:

public class BOUpsertEmployeeRequest {
    public BOUpsertEmployeeRequest () { }

    @NotNull
    @Property(name = "xref_code")
    public String xrefCode;

    @Property(name = "first_name")
    public String firstName;

    @Property(name = "last_name")
    public String lastName;

    @Property(name = "email_address")
    public String emailAddress;

    @Property(name = "phone")
    public String phone;

    @Property(name = "address")
    public List<String> address;

    @Property(name = "employment_status")
    public String employmentStatus;

    @Property(name = "pay_type")
    public String payType;

    @Property(name = "position")
    public String position;

    @Property(name = "skills")
    public List<String> skills;

    @Property(name = "gender")
    public String gender;
}

据我所知它具有默认构造函数。有人知道问题是什么吗?

谢谢!

解决方法

方法hasIgnoreMarker不仅被字段调用,还被构造函数调用,包括虚拟的:

调用此方法来检查给定的属性是否标记为忽略。用于确定是否按属性忽略属性,通常将来自多个访问器(获取器,设置器,字段,构造函数参数)的注释组合在一起。

在这种情况下,您应该仅忽略未正确标记的字段

static class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
    @Override
    public PropertyName findNameForDeserialization(Annotated a) {
        Property property = a.getAnnotation(Property.class);
        if (property == null) {
            return PropertyName.USE_DEFAULT;
        } else {
            return PropertyName.construct(property.name());
        }
    }

    @Override
    public boolean hasIgnoreMarker(AnnotatedMember m) {
        return m instanceof AnnotatedField
                && m.getAnnotation(Property.class) == null;
    }
}

示例:

class Pojo {
//    @Property(name = "id")
    Integer id;
//    @Property(name = "number")
    Integer number;
    @Property(name = "assure")
    Boolean assure;
    @Property(name = "person")
    Map<String,String> person;
}
String json =
        "{\"id\" : 1,\"number\" : 12345,\"assure\" : true," +
        " \"person\" : {\"name\" : \"John\",\"age\" : 23}}";

ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
Pojo pojo = mapper.readValue(json,Pojo.class);

System.out.println(pojo);
Pojo{id=null,number=null,assure=true,person={name=John,age=23}}

注意:自定义Property注释应具有RetentionPolicy.RUNTIME(与JsonProperty注释相同):

@Retention(RetentionPolicy.RUNTIME)
public @interface Property {
    String name();
}
,

您将很容易找到解决方案。

只需删除类BOUpsertEmployeeRequest的构造函数,就行:

public BOUpsertEmployeeRequest () { }

Jackson将能够解析您的JSON。

默认构造函数是由编译器自动生成的无参数构造函数,除非您定义了另一个构造函数:如果您定义了它,则它不是默认构造函数。

请查看以下链接以获取详细说明:Java default constructor

,

我将建议一种不同的方法:

在运行时中,使用字节码检测库Byte Buddy及其Java代理,使用正确的Jackson注释对字段进行重新注释。只需通过反射实现逻辑。请参见以下示例:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.DynamicType.Builder.FieldDefinition.Valuable;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyJsonIgnore {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyJsonProperty {
  String name();
}
public class Sample {
  public static void main(String[] args) throws JsonProcessingException {
    ByteBuddyAgent.install();
    ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
    ByteBuddy byteBuddy = new ByteBuddy();
    AnnotationDescription jsonIgnoreDesc =
        AnnotationDescription.Builder.ofType(JsonIgnore.class).build();

    Builder<Person> personBuilder = byteBuddy.redefine(Person.class);

    for (Field declaredField : Person.class.getDeclaredFields()) {
      Valuable<Person> field = personBuilder.field(ElementMatchers.named(declaredField.getName()));

      MyJsonProperty myJsonProperty = declaredField.getAnnotation(MyJsonProperty.class);
      if (myJsonProperty != null) {
        AnnotationDescription jsonPropertyDesc =
            AnnotationDescription.Builder.ofType(JsonProperty.class)
                .define("value",myJsonProperty.name())
                .build();
        personBuilder = field.annotateField(jsonPropertyDesc);
      }

      MyJsonIgnore myJsonIgnore = declaredField.getAnnotation(MyJsonIgnore.class);
      if (myJsonIgnore != null) {
        personBuilder = field.annotateField(jsonIgnoreDesc);
      }
    }

    personBuilder.make().load(Sample.class.getClassLoader(),classReloadingStrategy);

    Person person = new Person("Utku","Ozdemir","Berlin");
    
    ObjectMapper objectMapper = new ObjectMapper();
    String jsonString = objectMapper.writeValueAsString(person);
    System.out.println(jsonString);
  }
}
class Person {
  @MyJsonProperty(name = "FIRST")
  private String firstName;

  @MyJsonProperty(name = "LAST")
  private String lastName;

  @MyJsonIgnore private String city;

  public Person(String firstName,String lastName,String city) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.city = city;
  }
}

在上面的示例中,我

  • 创建MyJsonPropertyMyJsonIgnore批注以及一个Person类用于演示
  • 使用Byte buddy代理指示当前的Java进程
  • 创建一个字节码生成器以重新定义Person
  • Person类的字段上循环并检查这些注释
  • 为每个字段(在构建器上),Jackson的JsonProperty(具有正确的字段名称映射)和JsonIgnore添加一个附加注释。
  • 完成这些字段后,使用字节伙伴代理的类重载机制创建新的类字节码并将其加载到当前的类加载器中
  • 将个人对象写入标准输出。

它按预期打印:

{"FIRST":"Utku","LAST":"Ozdemir"}

(字段city被忽略)

此解决方案可能感觉像是一个过大的解决方案,但另一方面,它是非常通用的解决方案-只需更改一些逻辑,您就可以处理所有第三方类(您无法修改),而不是逐案处理。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...