如何让jackson与kotlin友好相处

jackson是个常用的java json库,功能很强大。

kotlin中有一个很好的特性叫data classlombok@Data很类似,会自动帮类生成getter/setter/hashCode/equal/toString。

语法也很简洁

data class Foo(val bar:String, val fuck:Int)

但是当我们要同时使用data class和jackson的时候问题就来了。

问题一、构造

直接对上面Foo类进行反序列化时

ObjectMapper().readValue("""{"bar":"a","fuck":1}""", Foo::class.java)

会出现类似异常

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of test.Foo: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"bar":"a","fuck":1}; line: 1, column: 2]
	at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
	at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1469)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
	...

其实看原因很简单,没有默认构造方法或JsonCreator的方法。

问题二、命名

当我们想对字段自定义json key的时候,通常会使用@JsonProperty来指定field name。就像下面这样

data class Foo(val bar:String, @JsonProperty("a") val fuck:Int)

但是会发现注解好像一点作用都没有

问题三、自定义序列化

而当我们相对某些字段采用特殊序列化方法的时候,会用@JsonSerialize/@JsonDeserialize

data class Foo(val bar:String, @JsonSerialize(using = XXX::class) val fuck:Int)

但是也会发现注解好像一点作用都没有

问题探究

这时候我们来面向google编程,通常会有以下几种方案

  • https://github.com/FasterXML/jackson-module-kotlin 使用jackson的kotlin module,让jackson能够识别kotlin的类型信息
  • 使用Mixin给每个类指定构造方法
  • 给data class增加默认构造函数,方法通常是kotlin-noarg插件或给每个字段设置默认值
  • 使用@get:JsonSerialize @set/get:JsonProperty之类的特殊注解

其实用jackson官方的kotlin模块是最简单有效的方法,但是对于我们目前遇到的场景有一个非常致命的问题,那就是我们会使用proguard来混淆kotlin代码。一般来说是没有问题的,但是jackson kotlin module会从kotlin编译器给每个类添加的@Metadata注解获取反射信息,而这个注解内的内容proguard混淆时不会处理。最终会导致混淆后的代码经常出现各种ClassNotFoundException。所以我们目前的场景不能使用这种方案。

最后还是只能使用基于jvm的基础方法。

kotlin的类声明结构

虽然kotlin号称对java 100%兼容,但是语法上由于多了很多东西,所以实际兼容的时候还会有很多技巧在其中,下面的样例代码其实就很好的解释了为什么平时在Java中的注解对kotlin一点用处都没有

@AnnotationClass
data class Foo @AnnotationConstructorMethod constructor(
    @AnnotationConstructorParameter
	val field : String
) {
    @AnnotationMethod fun method1(@AnnotationParam param:Int) {}
}

像是@JsonCreator这种需要在构造函数等方法上使用的注解,需要在类名后增加constructor关键字来使用。

而对类构造参数直接添加的注解实际上是被当成构造函数参数的注解对待的,所以这时候并不起真正的作用。而我们可以通过kotlin的use-site target特性来给具体字段的不同场景增加注解。

class Example(@field:Ann val foo,    // annotate Java field
              @get:Ann val bar,      // annotate Java getter
              @param:Ann val quux)   // annotate Java constructor parameter

就像这样在普通的注解前根据需要增加限定范围。所以当了解到这里的之后我们就知道,并不是jackson和kotlin不兼容,只是我们使用的姿势不对。

当我们能正确使用kotlin的注解之后,不管是jackson还是别的库,很多问题也就迎刃而解了。

具体详细注解文档可以参见官方文档

最后给个例子

Java类如下

@AnnotationClass
public class Demo {
    @AnnotationField
    private String field;

    @AnnotationGetter
    public String getField() {
        return field;
    }

    @AnnotationSetter
    public void setField(@AnnotationConstructorParam String field) {
        this.field = field;
    }

    @AnnotationConstructor
    public Demo(@AnnotationConstructorParam String field) {
        this.field = field;
    }

    @AnnotationConstructor
    public Demo(@AnnotationConstructorParam String field, @AnnotationConstructorParam String field2) {
        this.field = field;
        this.field2 = field2;
    }

    @AnnotationField
    private String field2 = "1";

    @AnnotationGetter
    public String getField2() {
        return field2;
    }

    @AnnotationSetter
    public void setField2(@AnnotationSetterParam String field2) {
        this.field2 = field2;
    }
}

可以转化成如下kotlin类,当然并不是只有这一种方式。

@AnnotationClass
data class Demo @AnnotationConstructor constructor(
        @field:AnnotationField
        @get:AnnotationGetter
        @set:AnnotationSetter
        @AnnotationConstructorParam
        var field: String? = null
) {
    @AnnotationField
    @get:AnnotationGetter
    @set:AnnotationSetter
    var field2: String = "1"

    @AnnotationConstructor
    constructor(@AnnotationConstructorParam field: String, @AnnotationConstructorParam field2: String) : this(field) {
        this.field2 = field2
    }
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿杜的世界

Java泛型的局限和使用经验泛型的局限泛型的常用经验参考资料

//使用泛型类 @Data @Builder @AllArgsConstructor @NoArgsConstructor public class Data...

742
来自专栏python学习指南

Python爬虫(十六)_JSON模块与JsonPath

本篇将介绍使用,更多内容请参考:Python学习指南 数据提取之JSON与JsonPATH JSON(JavaScript Object Notation...

3215
来自专栏玄魂工作室

如何学python-第三课 基础字符串操作

在上一篇文章中,我们学习了有关变量和输出的一些基础知识。大家应该还记得在上一篇文章中出现的字符串类型(string)吧!说白了,string类型其实就是一堆单词...

2609
来自专栏java架构师

WCF学习笔记(三)

今天遇到了一个很牛X的问题,多人一起解决很久,无果。 然,锲而不舍,一下午,从网络海洋里捞啊捞,终于觅得善果。 感谢这位大神,原文地址:http://hi.ba...

2736
来自专栏LEo的网络日志

valgrind使用:检测非法读写内存

38910
来自专栏葡萄城控件技术团队

检测字节流是否是UTF8编码

几天前偶尔看到有人发帖子问“如何自动识别判断url中的中文参数是GB2312还是Utf-8编码” 也拜读了wcwtitxu使用巨牛的正则表达式检测UTF8编码的...

2448
来自专栏编码小白

tomcat源码解读三(1) tomcat的jmx管理

    JMX即Java 管理扩展(Java Management Extensions,JMX)用来管理检测 Java 程序(同时 JMX 也在 J2EE 1...

3678
来自专栏吴伟祥

Spring4的新特性 Bean Validation1.1

603
来自专栏Java技术栈

关于Java序列化你应该知道的一切

什么是序列化 我们的对象并不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术。 Java序列化技术正是将对象转变成一串由...

2815
来自专栏JavaEdge

jackson-databind最佳实践给出一个简单的POJOObjectMapper集合

3035

扫码关注云+社区