跳到主要内容

JSON Converter

Jimmer实体并非POJO,但可以靠org.babyish.jimmmer.jackson.ImmutableModule支持Jackson序列化。

提示

如果使用Jimmer的Spring Boot Starter,该ImmutableModule会被自动注册,无需用户关注。

然而有的时候,我们需要对某些属性的Jackson序列化行为进行定制,我们以两种情况来举例。

  • 雪花ID

  • 字段敏感

1. 雪花ID

雪花id会导致数值很大的int64类型,遗憾的是,JavaScript的数字类型无法表达其他语言中的int64大数字。因此我们不得不让这类id属性被序列化为字符串。

用法

@Entity
@public interface Book {

@Id
@GeneratedValue(generatorType = SnowflakeIdGenerator.class)
@JsonConverter(LongToStringConverter.class)
long id();

String name();

...省略其他属性...
}

其中SnowflakIdGenerator是用户提供的分布式雪花id生成器实现,不是我们讨论的重点。

这里,我们讨论被标记的那行代码:

  • @org.babyfish.jimmer.jackson.JsonConverter表示自定义Book.id属性的Jackson序列化/反序列化行为。

  • org.babyfish.jimmer.jackson.LongToStringConverter表示将long类型的值按照String来序列化/反序列化。

    LongToStringConveter是Jimmer内置的Converter,这里展示一下其源代码,我们就可以快熟了解如何自定义Converter

    package org.babyfish.jimmer.jackson;

    public class LongToStringConverter implements Converter<Long, String> {

    // 如何序列化
    @Override
    public String output(Long value) {
    return Long.toString(value);
    }

    // 如何反序列化
    @Override
    public Long input(String jsonValue) {
    return Long.parseLong(jsonValue);
    }
    }
提示

注意,

  • 这里使用的是Jimmer的注解而非Jackson注解,原因在于:这不仅仅是序列化框架Jackson的行为调整问题,还有

  • 无需在Converter内部考虑null值,null值永远不会作为Converter的任何方法的参数。

这里,我们绕过ORM,通过模拟数据展示效果。

Book book = Objects.createBook(draft -> {
draft.setId(1546434604146774987L);
draft.setName("SQL in Action");
});
System.out.println(book);

由于Jimmer实体的toString就是Jackson序列化,所以,我们可以直接打印并观察Jackson序列化的结果:

{
"id": "1546434604146774987",
"name": "SQL in Action"
}

可见,序列化后的id是字符串,而非数字,这样,就避开了JavaScript客户端无法解析大数字的问题。

自动应用于IdView属性

信息

@IdView属性是进阶映射才会讲解的内容,读者也可以先跳过本节,以后再回来看。

@Entity
public interface Author {

@ManyToMany(mappedBy = "authors")
List<Book> books();

@IdView("authors")
List<Long> bookIds();

...省略其他属性...
}

这里,bookIds属性并非普通的List<Long>集合,而是所有关联的Book对象的id的集合。

由于Book.id属性已经被注解@JsonConverter修饰,所以,无需为这里的bookIds属性指定任何注解,该属性也有这种能力。

同样,绕过ORM,通过模拟数据展示效果。

Author author = Objects.createAuthor(draft -> {
draft.setBookIds(
Arrays.asList(
1546434604146774987L,
1546434604146774988L
)
);
});
System.out.println(book);

由于Jimmer实体的toString就是Jackson序列化,所以,我们可以直接打印并观察Jackson序列化的结果:

{
"bookIds": [
"1546434604146774987",
"1546434604146774988"
]
}

可见,序列化后的bookIds属性的所有元素都是字符串,而非数字,这样,就避开了JavaScript客户端无法解析大数字的问题。

自动应用于DTO

信息

DTO是进阶映射才会讲解的内容,读者也可以先跳过本节,以后再回来看。

src/main/dto目录下新建任何一个扩展名为dto的文件,编辑其代码如下

export com.yourcompany.yourproject.model.Book
-> package com.yourcompany.yourproject.model.dto

BookView {
id
name
}

编译项目,生成的Java/Kotlin类型如下

@GeneratedBy(file = "<yourproject>/src/main/dto/Book.dto")
public class BookView {

private String id;

private String name;

...省略其他属性...
}

可见,编译后自动生成的DTO类型中id属性的类型为String,而非long。

最后,我们验证一下将实体转化为DTO对象,如下

Book book = Objects.createBook(draft -> {
draft.setId(1546434604146774987L);
draft.setName("SQL in Action");
});
System.out.println(new BookView(book));

输出如下

BookView(
id = "1546434604146774987",
name = "SQL in Action"
)

2. 脱敏

用法

假如User实体具备phone属性,表示用户的电话号码,我们现在为该属性进行脱敏处理。

比如,手机号显示为135****6792

信息

脱敏并不是一个单纯页面渲染问题,必须保证HTTP请求本身不返回敏感的数据

对于这个需求,没有内置的Converter可用,我们必须自定义Converter,编写UserPhoneConverter,如下:

public class UserPhoneConverter implements Converter<String, String> {

@Override
public String output(String value) {
return value.substring(0, 3) + "****" + value.substring(7);
}
}

然后,简单修改User实体即可

@Entity
public interface User {

@JsonConverter(UserPhoneConverter.class)
String phone();
}

兼顾输入

在上面的例子中,UserPhoneConverter只覆盖了output方法,没有覆盖input方法。

由于input方法的默认实现是抛出UnsupportedOperationException异常,因此,无法支持反序列化。

然而,实际项目中,可以会基于实体对象生成Input DTO以完成数据录入,而且这种情况下,客户端应该录入精确的手机号,因此,我们可以如此覆盖input方法。

@Override
public String input(String value) {
retur value;
}