前言
本文準確來講是探討如何用 jackson 來序列化 apache avro 對象,因為簡單用 jackson 來序列化 apache avro 對象會報錯。原因是序列化 schema getschema() 時會報錯,后面會講到,需要序列化時忽略該屬性。那么能不能在 getschema() 上加上 @jsonignore 來忽略該屬性呢?原理上是通的。不過手工修改的 avsc 生成的 java 文件隨時會因為重新編譯而還原,所以不太具有實際可操作性,當然通過定制編譯 avsc 用的模板文件來加入 @jsonignore 是另一回事。
由于不能在要忽略的字段上添加 jsonignore 來控制,而如果我們明確了要忽略的字段類型的話,是能夠定制 jackson 的 objectmapper 來屏蔽某個特定的類型。來看下面序列化 apache avro 對象的例子:
假設我們有一個 apache 的 schema 文件 user.avsc, 內容如下:
1
2
3
4
5
6
7
8
9
|
{ "namespace" : "cc.unmi.data" , "type" : "record" , "name" : "user" , "fields" : [ { "name" : "name" , "type" : "string" }, { "name" : "address" , "type" : [ "string" , "null" ]} ] } |
編譯用 avro-tools compile schema user.avsc . 生成 cc.unmi.data.user.java 源文件,當我們試圖對類型的對象用 jackson 進行序列化時
1
2
3
|
objectmapper objectmapper = new objectmapper() ; user user = user.newbuilder().setname( "yanbin" ).setaddress( "chicago" ).build(); system.out.println(objectmapper.writevalueasstring(user)); |
收到異常(關鍵信息)
caused by: org.apache.avro.avroruntimeexception: not a map: {"type":"record","name":"user","namespace":"cc.unmi.data","fields":[{"name":"name","type":"string"},{"name":"address","type":["string","null"]}]}
at org.apache.avro.schema.getvaluetype(schema.java:294)
at com.fasterxml.jackson.databind.ser.beanpropertywriter.serializeasfield(beanpropertywriter.java:664)
at com.fasterxml.jackson.databind.ser.std.beanserializerbase.serializefields(beanserializerbase.java:689)
從上面的錯誤可以定位到 jackson 的試圖序列化 user 對象的
1
|
public org.apache.avro.schema getschema() { return schema$; } |
而 org.apache.avro.schema 中的 getvaluetype() 直接拋出異常拒絕被歸化
1
2
3
|
public schema getvaluetype() { throw new avroruntimeexception( "not a map: " + this ); } |
因此,要實現序列化 apache avro 對象,解決的辦法有三
- 凡是 org.apache.avro.schema 的屬性不被序列化(schema 輸出確實用處不大)
- 或對于org.apache.avro.schema 類型的屬性定制序列化,比如輸出為完整類名,或 schema 定義的文本內容
- 再來一個,對 specificrecordbase 類型的 schema 名稱的屬性進行忽略(avro 類型繼承自 specificrecordbase)
它們的實現分別如下
忽略序列化指定類型的屬性
先定義一個標注了 @jsonignoretype 的注解
1
2
3
|
@jsonignoretype @interface ignoreavroschemafield { } |
序列化 apache avro 對象前給 objectmapp 加一個 mixin
1
2
3
4
5
|
objectmapper objectmapper = new objectmapper() ; objectmapper.addmixin(schema. class , ignoreavroschemafield. class ); user user = user.newbuilder().setname( "yanbin" ).setaddress( "chicago" ).build(); system.out.println(objectmapper.writevalueasstring(user)); |
有了上面高度行的代碼,這兒的 apache avro user 對象就能被正常序列化了,輸出為
{"name":"yanbin","address":"chicago"}
這樣 getschema() 返回的類型,或另何對象中有 org.apache.avro.schema 類型的屬性都會在序列化時忽略掉
定制 schema 屬的輸出內容
對于 schema 類型的屬性,除了前面采取堵的方式,還可以因利疏導,即定制 schema 屬性值的輸出內容
定制化 schema 序列化方式
1
2
3
4
5
6
7
|
class avroschemaserializer extends jsonserializer<schema> { @override public void serialize(schema value, jsongenerator jgen, serializerprovider provider) throws ioexception { jgen.writestring(value.getfullname()); //直接輸出當前 apache avro 對象的全限類名 } } |
給 objectmapper 加上定制的序列化器
1
2
3
4
5
6
7
|
objectmapper objectmapper = new objectmapper() ; simplemodule simplemodule = new simplemodule( "simplemodule" , version.unknownversion()); simplemodule.addserializer(schema. class , new avroschemaserializer()); objectmapper.registermodule(simplemodule); user user = user.newbuilder().setname( "yanbin" ).setaddress( "chicago" ).build(); system.out.println(objectmapper.writevalueasstring(user)); |
序列化后產生的輸出如下
{"name":"yanbin","address":"chicago","schema":"cc.unmi.data.user"}
如果在 avroschemaserializer 把 jgen.writestring(value.getfullname()) 替換如下
1
|
jgen.writestring(value.tostring()); |
并且序列化后對內容進行格式化輸出
1
2
3
4
5
6
|
system.out.println(objectmapper.writerwithdefaultprettyprinter().writevalueasstring(user)); { "name" : "yanbin" , "address" : "chicago" , "schema" : "{\"type\":\"record\",\"name\":\"user\",\"namespace\":\"cc.unmi.data\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"address\",\"type\":[\"string\",\"null\"]}]}" } |
指定特定對象的屬性名進行過濾
從語義上除了 ignore 外,filter 也像是干這事的,可以嘗試過下面的方式, 分兩步走
定義一個帶 @jsonfilter 的注解,也是不顯示注解到任何類
1
2
|
@jsonfilter ( "filter out apache avro schema field" ) //字符串值要與下面 addfilter("xxx") 保持一致 class propertyfiltermixin {} |
給 objectmapper 設置 filter
1
2
3
4
5
6
7
8
|
objectmapper objectmapper = new objectmapper() ; objectmapper.addmixin(specificrecordbase. class , propertyfiltermixin. class ); //對 specificrecordbase 類型的對象應用 filterprovider filterprovider = new simplefilterprovider() //對 specificrecordbase 類型(如 user) 的名為 "schema" 屬性屏蔽 .addfilter( "filter out apache avro schema field" , simplebeanpropertyfilter.serializeallexcept( "schema" )); objectmapper.setfilterprovider(filterprovider); user user = user.newbuilder().setname( "yanbin" ).setaddress( "chicago" ).build(); system.out.println(objectmapper.writevalueasstring(user)); |
輸出效果沒有意外,也能避免序列化 schema 屬性
{"name":"yanbin","address":"chicago"}
這最后一種方式是本篇寫作行將結束時找到并驗證的,所以不寫出來,不進行梳理可能永遠只會第一種方法。
鏈接:
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://yanbin.blog/jackson-ignore-specified-field-type/