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

如何解决如何使用尊重自定义注释的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被忽略)

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-