前言
Gson是谷歌提供的解析Json的库,使用非常方便,并且采用了很多设计模式,是一个很优秀的工具库,因此我这里记录一下使用Gson的一些心得。
问题一
最近公司准备将项目的一个子业务接口换掉,奈何之前后端采用PHP开发,字段首字母全部大写,而后新接口采用驼峰式命名,因此字段更替是一个十分恼人的事情。不太想一个一个的去改JsonBean,所以想看看Json反序列化的过程中有没有什么取巧的方式。
为了解决该问题,我去看了下Gson的源码:
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
List<TypeAdapterFactory> factoriesToBeAdded) {
// ...省略一系列构造方法赋值操作
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
// ... 以下省略多种解析类型
// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
this.factories = Collections.unmodifiableList(factories);
}
这里我简单说一下后续会写一篇专门分析Gson源码的文章。Gson通过TypeAdapter去识别字段类型并解析,提前将一些常用的类型的解析实现类存储在集合中,常用类型保存完毕,之后最后添加了一个ReflectiveTypeAdapterFactory类,这个类专门用于解析自定义类型Json对象,我们注意去关注这个类。之前在使用Gson的时候,经常使用一个注解@SerializedName,这个可以将json字段名称转换成自定义名称,所以针对这次的业务问题我想从这里的源代码分析入手看看有没有好的切入点。
/**
* nameLi : JSON
* addressLi : 北京市西城区
* ageLi : 25
*/
// 自定义字段名称 可选字段名称 一般用于容错处理
@SerializedName(value = "nameLi1", alternate = {"nameLi2"})
private String NameLi;
private String AddressLi;
private int AgeLi;
/** first element holds the default name */
private List<String> getFieldNames(Field f) {
// 这里就是获取字段上的注解进行转义
SerializedName annotation = f.getAnnotation(SerializedName.class);
if (annotation == null) {
// 解决问题入口
String name = fieldNamingPolicy.translateName(f);
return Collections.singletonList(name);
}
// 可选名称
String serializedName = annotation.value();
String[] alternates = annotation.alternate();
if (alternates.length == 0) {
return Collections.singletonList(serializedName);
}
// 将字段的可选名称保存到集合中
List<String> fieldNames = new ArrayList<String>(alternates.length + 1);
fieldNames.add(serializedName);
for (String alternate : alternates) {
fieldNames.add(alternate);
}
return fieldNames;
}
这里发现当@SerializedName没有注解到字段上时会去调用一个FieldNamingPolicy,典型的策略设计模式。因此我们知道可以自定义一个命名解析规则,哈哈,可以不用加班了~
public interface FieldNamingStrategy {
/**
* Translates the field name into its JSON field name representation.
*
* @param f the field object that we are translating
* @return the translated field name.
* @since 1.3
*/
public String translateName(Field f);
}
可见是一个接口,因此我们实现它:
class MyFieldNamingStrategy : FieldNamingStrategy {
override fun translateName(f: Field): String {
val s = f.name
if (s == "VipNo") {
return "UserId"
}
return if (Character.isLowerCase(s[0])) {
s
} else {
Character.toLowerCase(s[0]).toString() + s.substring(1)
}
}
}
可见这样处理就完成了,我们通过反射更改Field的变量名称,还可以将VipNo 改为 UserId。注意的是还记得刚才的源码么 一旦字段被@SerializedName注解的,这里就会失效,目前针对我的问题只能手动改了,或者去掉注解,还好用的这个注解不多。
使用方式
val gson = GsonBuilder().setFieldNamingStrategy(MyFieldNamingStrategy()).create()
val testEntity = gson.fromJson(json, TestEntity::class.java)
其实这里Gson提供我们一些实现好的策略
// 枚举类型策略
public enum FieldNamingPolicy implements FieldNamingStrategy {
IDENTITY,// 默认的方式 接口字段是什么样式对应JsonBean就是什么字段样式
UPPER_CAMEL_CASE,// someFieldName ---> SomeFieldName 反过来竟然没实现😂
UPPER_CAMEL_CASE_WITH_SPACES,// someFieldName ---> Some Field Name
LOWER_CASE_WITH_UNDERSCORES,// someFieldName ---> some_field_name
LOWER_CASE_WITH_DASHES,// someFieldName ---> some-field-name
LOWER_CASE_WITH_DOTS // someFieldName ---> some.field.name
}
网友评论