前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何让jackson与kotlin友好相处

如何让jackson与kotlin友好相处

原创
作者头像
王沛文
修改2018-12-14 18:30:37
6.2K0
修改2018-12-14 18:30:37
举报
文章被收录于专栏:王沛文的专栏王沛文的专栏

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

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

语法也很简洁

代码语言:txt
复制
data class Foo(val bar:String, val fuck:Int)

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

问题一、构造

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

代码语言:txt
复制
ObjectMapper().readValue("""{"bar":"a","fuck":1}""", Foo::class.java)

会出现类似异常

代码语言:txt
复制
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。就像下面这样

代码语言:txt
复制
data class Foo(val bar:String, @JsonProperty("a") val fuck:Int)

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

问题三、自定义序列化

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

代码语言:txt
复制
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一点用处都没有

代码语言:txt
复制
@AnnotationClass
data class Foo @AnnotationConstructorMethod constructor(
    @AnnotationConstructorParameter
	val field : String
) {
    @AnnotationMethod fun method1(@AnnotationParam param:Int) {}
}

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

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

代码语言:txt
复制
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类如下

代码语言:txt
复制
@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类,当然并不是只有这一种方式。

代码语言:txt
复制
@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
    }
}

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题一、构造
  • 问题二、命名
  • 问题三、自定义序列化
  • 问题探究
    • kotlin的类声明结构
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档