Jackson 序列化忽略指定类型的属性
本文准确来讲是探讨如何用 Jackson 来序列化 Apache avro 对象,因为简单用 Jackson 来序列化 Apache avro 对象会报错。原因是序列化
由于不能在要忽略的字段上添加
假设我们有一个 Apache 的 Schema 文件
编译用
收到异常(关键信息)
而
因此,要实现序列化 Apache avro 对象,解决的办法有三
它们的实现分别如下
序列化 Apache avro 对象前给 ObjectMapp 加一个 mixin
有了上面高度行的代码,这儿的 Apache avro
这样
定制化
给 ObjectMapper 加上定制的序列化器
序列化后产生的输出如下
并且序列化后对内容进行格式化输出
指定特定对象的属性名进行过滤
从语义上除了
定义一个带
给 ObjectMapper 设置 filter
输出效果没有意外,也能避免序列化
链接:
永久链接 https://yanbin.blog/jackson-ignore-specified-field-type/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
Schema getSchema() 时会报错,后面会讲到,需要序列化时忽略该属性。那么能不能在 getSchema() 上加上 @JsonIgnore 来忽略该属性呢?原理上是通的。不过手工修改的 avsc 生成的 Java 文件随时会因为重新编译而还原,所以不太具有实际可操作性,当然通过定制编译 avsc 用的模板文件来加入 @JsonIgnore 是另一回事。由于不能在要忽略的字段上添加
JsonIgnore 来控制,而如果我们明确了要忽略的字段类型的话,是能够定制 Jackson 的 ObjectMapper 来屏蔽某个特定的类型。来看下面序列化 Apache avro 对象的例子:假设我们有一个 Apache 的 Schema 文件
user.avsc, 内容如下:1{
2 "namespace": "cc.unmi.data",
3 "type": "record",
4 "name": "User",
5 "fields": [
6 {"name": "name", "type": "string"},
7 {"name": "address", "type": ["string", "null"]}
8 ]
9}编译用
avro-tools compile schema user.avsc . 生成 cc.unmi.data.User.java 源文件,当我们试图对类型的对象用 Jackson 进行序列化时1ObjectMapper objectMapper = new ObjectMapper() ;
2User user = User.newBuilder().setName("Yanbin").setAddress("Chicago").build();
3System.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"]}]}从上面的错误可以定位到 Jackson 的试图序列化
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)
User 对象的1 public org.apache.avro.Schema getSchema() { return SCHEMA$; }而
org.apache.avro.Schema 中的 getValueType() 直接抛出异常拒绝被归化1 public Schema getValueType() {
2 throw new AvroRuntimeException("Not a map: "+this);
3 }因此,要实现序列化 Apache avro 对象,解决的办法有三
- 凡是
org.apache.avro.Schema的属性不被序列化(Schema 输出确实用处不大) - 或对于
org.apache.avro.Schema类型的属性定制序列化,比如输出为完整类名,或 Schema 定义的文本内容 - 再来一个,对
SpecificRecordBase类型的schema名称的属性进行忽略(avro 类型继承自 SpecificRecordBase)
它们的实现分别如下
忽略序列化指定类型的属性
先定义一个标注了@JsonIgnoreType 的注解1@JsonIgnoreType
2@interface IgnoreAvroSchemaField {
3}序列化 Apache avro 对象前给 ObjectMapp 加一个 mixin
1ObjectMapper objectMapper = new ObjectMapper() ;
2objectMapper.addMixIn(Schema.class, IgnoreAvroSchemaField.class);
3
4User user = User.newBuilder().setName("Yanbin").setAddress("Chicago").build();
5System.out.println(objectMapper.writeValueAsString(user));有了上面高度行的代码,这儿的 Apache avro
User 对象就能被正常序列化了,输出为{"name":"Yanbin","address":"Chicago"}
这样
getSchema() 返回的类型,或另何对象中有 org.apache.avro.Schema 类型的属性都会在序列化时忽略掉定制 Schema 属的输出内容
对于 Schema 类型的属性,除了前面采取堵的方式,还可以因利疏导,即定制 Schema 属性值的输出内容定制化
Schema 序列化方式1class AvroSchemaSerializer extends JsonSerializer<Schema> {
2
3 @Override
4 public void serialize(Schema value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
5 jgen.writeString(value.getFullName()); //直接输出当前 Apache avro 对象的全限类名
6 }
7}给 ObjectMapper 加上定制的序列化器
1ObjectMapper objectMapper = new ObjectMapper() ;
2SimpleModule simpleModule = new SimpleModule("SimpleModule", Version.unknownVersion());
3simpleModule.addSerializer(Schema.class, new AvroSchemaSerializer());
4objectMapper.registerModule(simpleModule);
5
6User user = User.newBuilder().setName("Yanbin").setAddress("Chicago").build();
7System.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 System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));{如果 JSON 中仍有
"name" : "Yanbin",
"address" : "Chicago",
"schema" : "{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"cc.unmi.data\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"address\",\"type\":[\"string\",\"null\"]}]}"
}
schema 属性的话,在反序列化该属性时会出错,因为类型不一致了,解决办法是要为反序列化用的 ObjectMapper 设置1objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);指定特定对象的属性名进行过滤
从语义上除了
Ignore 外,Filter 也像是干这事的,可以尝试过下面的方式, 分两步走定义一个带
@JsonFilter 的注解,也是不显示注解到任何类1@JsonFilter("filter out apache avro schema field") //字符串值要与下面 addFilter("xxx") 保持一致
2class PropertyFilterMixIn {}给 ObjectMapper 设置 filter
1 ObjectMapper objectMapper = new ObjectMapper() ;
2 objectMapper.addMixIn(SpecificRecordBase.class, PropertyFilterMixIn.class); //对 SpecificRecordBase 类型的对象应用
3 FilterProvider filterProvider = new SimpleFilterProvider() //对 SpecificRecordBase 类型(如 User) 的名为 "schema" 属性屏蔽
4 .addFilter("filter out apache avro schema field", SimpleBeanPropertyFilter.serializeAllExcept("schema"));
5 objectMapper.setFilterProvider(filterProvider);
6
7 User user = User.newBuilder().setName("Yanbin").setAddress("Chicago").build();
8 System.out.println(objectMapper.writeValueAsString(user));输出效果没有意外,也能避免序列化
schema 属性{"name":"Yanbin","address":"Chicago"}这最后一种方式是本篇写作行将结束时找到并验证的,所以不写出来,不进行梳理可能永远只会第一种方法。
链接:
永久链接 https://yanbin.blog/jackson-ignore-specified-field-type/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。