开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
引言
公司业务里涉及到 Proto 生成的类很多,原因是我们需要和其他部门进行 Grpc 通信,用的也是 proto + grpc 这一套,相信知道的同学都会被 proto 生产的类和 pojo 类转换问题而困扰,首先为了解耦都会用 dto 去转一层,我们当时用的 BeanUtils 进行对象直接的拷贝赋值,但是它有个弊端就是无法深拷贝,而且数组的拷贝也有些不雅,于是我调研了下Google 的Gson,想着 Gson 和 Proto 是一家的应该有相关联的操作吧,然后不出我所料
依赖
首先你要导入依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>
<!-- Protobuf与Json的相互转化 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.17.2</version>
</dependency>
工具类如下,粘贴即用
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.protobuf.LazyStringArrayList;
import com.google.protobuf.LazyStringList;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import lombok.SneakyThrows;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @desc: JavaBean 和 ProtoBean 相互转换工具类
* 适用场景
* 1、JavaBean 转 ProtoBean 、 ProtoBean 转 JavaBean 、JavaBean 装 JavaBean JavaBeanList 转 JavaBeanList
* <p>
* 2、JavaBeanList 转 ProtoBeanList 、ProtoBeanList 转 JavaBeanList
* <p>
* 3、JavaBean 转 ProtoBuilder 、 JavaBeanList 转 ProtoBuilderList
* <p>
* 4、ProtoBean 转 ProtoBean 、ProtoBeanList 转 ProtoBeanList
* <p>
* 5、Json 转 JavaBean 、JavaBean 转 Json 、Json 转 JavaBeanList
*/
public class ProtoJsonUtils {
private static Gson gson = getGson();
/**
* protoBean -> string
*
* @param sourceMessage protoBean
* @return string
*/
public static String protoToJson(MessageOrBuilder sourceMessage) {
try {
return JsonFormat.printer().print(sourceMessage);
} catch (Exception e) {
e.printStackTrace();
throw new HexServerException("ProtoJsonUtils->toJson->errorMessage:proto序列化string异常");
}
}
/**
* @desc: String 转 Builder对象
*/
private static <M extends Message.Builder> void jsonToBuilder(M targetBuilder, String json) {
try {
JsonFormat.parser().ignoringUnknownFields().merge(json, targetBuilder);
} catch (Exception e) {
e.printStackTrace();
throw new HexServerException("ProtoJsonUtils->JsonToBuilder->errorMessage:json 转 Builder对象异常");
}
}
/**
* @desc: ProtoBean 转 JavaBean
* messageOrBuilder protoBean
* target javaBean 的 Class对象
*/
public static <T, M extends MessageOrBuilder> T protoToBean(M messageOrBuilder, Class<T> target) {
String protoJson = protoToJson(messageOrBuilder);
Gson gson = getGson();
return gson.fromJson(protoJson, target);
}
/**
* @desc: ProtoBeanList 转 JavaBeanList
*/
public static <T, M extends MessageOrBuilder> List<T> protoListToBeanList(List<M> messageOrBuilders, Class<T> target) {
List<T> list = new ArrayList<>();
for (M messageOrBuilder : messageOrBuilders) {
T t = protoToBean(messageOrBuilder, target);
list.add(t);
}
return list;
}
/**
* @desc: ProtoBean 转 ProtoBean
*/
public static <T extends Message, M extends Message> T protoToProto(M messageOrBuilder, Class<T> protoClass) {
try {
String protoJson = protoToJson(messageOrBuilder);
Constructor<T> declaredConstructor = protoClass.getDeclaredConstructor((Class<?>[]) null);
declaredConstructor.setAccessible(Boolean.TRUE);
T instance = declaredConstructor.newInstance();
Message.Builder builder = instance.toBuilder();
jsonToBuilder(builder, protoJson);
return (T) builder.build();
} catch (Exception e) {
e.printStackTrace();
throw new HexServerException("ProtoJsonUtils->protoToProto->errorMessage:ProtoBean 转 ProtoBean 异常");
}
}
/**
* @desc: ProtoListBean 转 ProtoListBean
*/
public static <T extends Message, M extends Message> List<T> protoListToProtoList(List<M> messageOrBuilders, Class<T> protoClass) {
List<T> list = new ArrayList<>();
for (M messageOrBuilder : messageOrBuilders) {
T t = protoToProto(messageOrBuilder, protoClass);
list.add(t);
}
return list;
}
/**
* @desc: JavaBean 转 JavaBean (单个对象)
*/
public static <T, M> T beanToBean(M source, Class<T> target) {
Gson gson = getGson();
String json = gson.toJson(source);
return gson.fromJson(json, target);
}
/**
* @desc: JavaBeanList 转 JavaBeanList
*/
public static <T> List<T> beanToBean(Collection<?> source, Class<T> target) {
Gson gson = getGson();
String json = gson.toJson(source);
return gson.fromJson(json, new ParameterizedTypeImpl(target));
}
/**
* @desc: javaBean 转 Json
*/
public static String beanToJson(Object o) {
Gson gson = getGson();
return gson.toJson(o);
}
/**
* @desc: json 转 JavaBean
*/
public static <T> T jsonToBean(String jsonString, Class<T> clazz) {
Gson gson = getGson();
return gson.fromJson(jsonString, clazz);
}
public static <T> List<T> jsonToBeanList(String json, Class<T> clazz) {
Type type = new ParameterizedTypeImpl(clazz);
Gson gson = getGson();
return gson.fromJson(json, type);
}
/**
* @desc: JavaBean 转 Builder对象
*/
public static <M extends Message.Builder, T> M beanToBuilder(T beanSource, Class<M> targetBuilderClass) {
try {
Constructor<M> declaredConstructor = targetBuilderClass.getDeclaredConstructor((Class<?>[]) null);
declaredConstructor.setAccessible(Boolean.TRUE);
M instance = declaredConstructor.newInstance();
Gson gson = getGson();
String json = gson.toJson(beanSource);
jsonToBuilder(instance, json);
return instance;
} catch (Exception e) {
e.printStackTrace();
throw new HexServerException("ProtoJsonUtils->beanToBuilder->errorMessage:JavaBean 转 Builder对象异常");
}
}
/**
* @desc: javaBean 转 Proto
*/
@SneakyThrows
public static <M extends Message, T> M beanToProto(T bean, Class<M> targetProtoClass) {
Constructor<M> declaredConstructor = targetProtoClass.getDeclaredConstructor((Class<?>[]) null);
declaredConstructor.setAccessible(Boolean.TRUE);
M instance = declaredConstructor.newInstance();
Gson gson = getGson();
String json = gson.toJson(bean);
Message.Builder builder = instance.toBuilder();
jsonToBuilder(builder, json);
return (M) builder.build();
}
/**
* @desc: javaBean 列表 转 ProtoBean 列表
*/
public static <M, T extends Message> List<T> beanListToProtoList(Collection<M> beanSourceList, Class<T> protoBean) {
try {
List<T> resultList = new ArrayList<>();
for (Object bean : beanSourceList) {
Constructor<T> declaredConstructor = protoBean.getDeclaredConstructor((Class<?>[]) null);
declaredConstructor.setAccessible(Boolean.TRUE);
T instance = declaredConstructor.newInstance();
Class<? extends Message.Builder> aClass = instance.toBuilder().getClass();
Message.Builder builder = beanToBuilder(bean, aClass);
resultList.add((T) builder.build());
}
return resultList;
} catch (Exception e) {
e.printStackTrace();
throw new HexServerException("ProtoJsonUtils->beanListToProtoList->errorMessage:javaBean 列表 转 ProtoBean 列表异常");
}
}
private static Gson getGson() {
if (gson != null) {
return gson;
}
gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
.registerTypeAdapter(LazyStringList.class, new TypeAdapter<LazyStringList>() {
@Override
public void write(JsonWriter jsonWriter, LazyStringList strings) throws IOException {
}
@Override
public LazyStringList read(JsonReader in) throws IOException {
LazyStringList lazyStringList = new LazyStringArrayList();
in.beginArray();
while (in.hasNext()) {
lazyStringList.add(in.nextString());
}
in.endArray();
return lazyStringList;
}
})
.create();
return gson;
}
public static final class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
@Override
public JsonElement serialize(LocalDate date, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
@Override
public LocalDate deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
String timestamp = element.getAsJsonPrimitive().getAsString();
return LocalDate.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
public static final class LocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> {
@Override
public JsonElement serialize(LocalDateTime date, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
@Override
public LocalDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
String timestamp = element.getAsJsonPrimitive().getAsString();
return LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}
private static class ParameterizedTypeImpl implements ParameterizedType {
Class clazz;
public ParameterizedTypeImpl(Class clz) {
clazz = clz;
}
@Override
public Type[] getActualTypeArguments() {
return new Type[]{clazz};
}
@Override
public Type getRawType() {
return List.class;
}
@Override
public Type getOwnerType() {
return null;
}
}
}
自定义序列化适配器
上面有些是自定义的一些序列化器,可以根据自己的需求进行属性不同类型的转换,比如有时候前端传数组[1,2],后端保存成 "1,2"的方式存储到DB,然后查询的时候反过来,这时候就需要自定义序列化器
- 自定义序列化器
public static final class JsonListStringAdapter implements JsonSerializer<String>, JsonDeserializer<String> {
@Override
public JsonElement serialize(String string, Type typeOfSrc, JsonSerializationContext context) {
JsonArray jsonArray = new JsonArray();
String[] split = string.split(",");
for (String s : split) {
jsonArray.add(s);
}
return jsonArray;
}
@Override
public String deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray jsonArray = element.getAsJsonArray();
List<String> result = new ArrayList<>();
for (JsonElement jsonElement : jsonArray) {
result.add(jsonElement.getAsString());
}
return String.join(",", result);
}
}
- 如果只想某个字段使用 需要在属性上加 @JsonApapter
/**
* 适用渠道,多个用逗号隔开
*/
@JsonAdapter(value = ProtoJsonUtils.JsonListStringAdapter.class)
private String suitChannel;
- 所有字段都走这个序列化器,把这个注册进去
private static Gson getGson() {
if (gson != null) {
return gson;
}
gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
.registerTypeAdapter(LazyStringList.class, new TypeAdapter<LazyStringList>() {
@Override
public void write(JsonWriter jsonWriter, LazyStringList strings) throws IOException {
}
@Override
public LazyStringList read(JsonReader in) throws IOException {
LazyStringList lazyStringList = new LazyStringArrayList();
in.beginArray();
while (in.hasNext()) {
lazyStringList.add(in.nextString());
}
in.endArray();
return lazyStringList;
}
})
.create();
return gson;
}
JsonSerializer
序列化接口 bean 序列化的时候需要用到
JsonDeserializer
反序列化接口 bean 返序列化的时候需要用到
JsonElement
承载序列化的字段类型
- JsonArray 数组
- JsonNull 空对象
- JsonObject 对象
- JsonPrimitive 原生字段