首页 > 其他分享 >更优雅的使用Gson解析Json

更优雅的使用Gson解析Json

时间:2024-05-10 11:58:22浏览次数:10  
标签:序列化 null add 优雅 Json factories Gson TypeAdapter

 

Gson背靠Google这棵大树,拥有广泛的社区支持和相对丰富的文档资源,同时因其简单直观的API,一直以来基本稳坐Android开发序列化的头把交椅(直到Google宣布kotlin成为Android开发的首选语言)。本文对Gson的使用及主要流程做下分析。

Gson的基本使用

Gson依赖

  kotlin 复制代码
dependencies {
  implementation 'com.google.code.gson:gson:2.10.1'
}

下文的Gson源码同样基于v2.10.1版本

配置Gson

Gson类是整个Gson类库的核心类。开发者只需要通过调用new Gson()即可获取到gson实例,但我建议你通过GsonBuilder来创建并配置Gson类的实例:

  kotlin 复制代码
private val gson = GsonBuilder()
        // 为特定类型注册自定义的序列化器或反序列化器(不支持协变)
        .registerTypeAdapter(Boolean::class.java, BooleanTypeAdapter())
        // 为特定类型注册自定义的序列化器或反序列化器(支持协变)
        .registerTypeHierarchyAdapter(xxxx)
        // 注册一个能够为多种类型提供适配器的工厂
        .registerTypeAdapterFactory(xxxx)
        // 设置长整型(Long)字段的序列化策略,例如将其序列化为字符串而不是数字
        .setLongSerializationPolicy(LongSerializationPolicy.STRING)
        // 自定义日期/时间字段的序列化格式
        .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
        // 设置字段命名策略,以控制字段如何映射到JSON键名(默认不改变命名风格)
        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        // 设置自定义的字段命名策略,用于控制字段如何映射到JSON键名
        .setFieldNamingStrategy(xxxx)
        // 排除具有特定Java修饰符(默认 transient 和 static)的字段
        .excludeFieldsWithModifiers(java.lang.reflect.Modifier.TRANSIENT or java.lang.reflect.Modifier.STATIC)
        // 设置只序列化和反序列化带有@Expose注解的字段
        .excludeFieldsWithoutExposeAnnotation()
        // 设置类或字段过滤规则
        .setExclusionStrategies(xxxx)
        // 设置过滤规则(只适用于序列化)
        .addSerializationExclusionStrategy(xxxx)
        // 设置过滤规则(只适用于反序列化)
        .addDeserializationExclusionStrategy(xxxx)
        // 设置版本号,Gson将忽略所有高于此版本号的@Since注解和@Until注解的字段
        .setVersion(1.0)
        // 启用非基础类型 Map Key
        .enableComplexMapKeySerialization()
        // 默认情况下,Gson在序列化时会忽略值为null的字段。启用该设置后,Gson将包括值为null的字段
        .serializeNulls()
        // Gson将以更易读的格式输出JSON字符串,即格式化后的JSON,其中包含换行符和缩进。
        .setPrettyPrinting()
        .create()

Gson实例在调用JSON进行序列化/反序列化操作的过程中不维护任何状态,不同的Gson实例的配置和缓存等也不会复用,我们可以自由地使用同一个Gson实例对多个JSON进行序列化/反序列化操作。因此,我们应该在项目中提供一个全局的Gson实例,避免创建冗余的Gson实例。

这些设置提供了强大的定制能力,使得Gson能够适应各种不同的序列化和反序列化需求。通过链式调用这些方法,开发者可以轻松地构建出满足特定需求的Gson实例。

完成以上配置,你就可以在项目中愉快的使用Gson完成序列化/反序列化工作了:

  java 复制代码
// Serialization
Gson gson = new Gson();
gson.toJson(1);            // ==> 1
gson.toJson("abcd");       // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values);       // ==> [1]

// Deserialization
int i = gson.fromJson("1", int.class);
Integer intObj = gson.fromJson("1", Integer.class);
Long longObj = gson.fromJson("1", Long.class);
Boolean boolObj = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] strArray = gson.fromJson("[\"abc\"]", String[].class);

Gson中的注解

Gson库提供了一些注解,通过这些注解可以更加灵活地控制Java对象到JSON字符串的序列化和反序列化过程。

  • @SerializedName:指定一个字段在JSON中的名称。常用于Java字段名和JSON键名不一致的情况。
  • @Expose:标记一个字段是否应该被序列化或反序列化。它用于在序列化/反序列化过程中包含或排除字段。
  • @Since:指定一个字段自某个版本号之后才被序列化或反序列化。这允许版本控制,可以用于向后兼容。
  • @Until:指定一个字段在某个版本号之前被序列化或反序列化。它与@Since注解相反,用于版本控制和向后兼容。
  • @JsonAdapter:指定一个字段使用自定义的序列化器和反序列化器。
  kotlin 复制代码
data class User(
	// username字段在序列化/反序列化时会使用name作为键名
    @SerializedName("name")
    val username: String,
	// 标记password不参与序列化/反序列化
    @Expose
    val password: String,
	// phoneNumber字段只有在版本号为1.1或更高时才会参与序列化/反序列化
    @Since(1.1)
    val phoneNumber: String,
	// email字段只有在版本号低于1.2时才会参与序列化/反序列化
    @Until(1.2)
    val email: String,
	// 使用自定义的GenderAdapter解析器序列化/反序列化gender字段
    @JsonAdapter(GenderAdapter::class)
    val gender: Gender,
)

enum class Gender {
    MALE,
    FEMALE,
    UNKNOWN,
}

class GenderAdapter: TypeAdapter<Gender>() {
    override fun write(out: JsonWriter, value: Gender) {
        when (value) {
            Gender.UNKNOWN -> out.nullValue()
            Gender.MALE -> out.value("1")
            Gender.FEMALE -> out.value("2")
        }
    }
    override fun read(`in`: JsonReader): Gender {
        return when (`in`.peek()) {
            JsonToken.NULL -> {
                `in`.nextNull()
                Gender.UNKNOWN
            }
            JsonToken.NUMBER -> {
                when(`in`.nextInt()) {
                    1 -> Gender.MALE
                    2 -> Gender.FEMALE
                    else -> Gender.UNKNOWN
                }
            }
            else -> Gender.UNKNOWN
        }
    }
}

自定义解析

对于序列化/反序列化,Gson提供了三个关键接口:JsonSerializerJsonDeserializer以及TypeAdapter,他们提供了不同级别的控制和灵活性。

JsonSerializer 和 JsonDeserializer

JsonSerializerJsonDeserializerGson1.x版本提供的用以自定义解析的两个接口,从名字也能明显看出来,JsonSerializer负责自定义序列化工作,而JsonDeserializer负责反序列化。

  • JsonSerializer 定义如何将类型T的对象转换成JSON。它只有一个方法serialize(T src, Type typeOfSrc, JsonSerializationContext context),返回一个JsonElement对象。
  • JsonDeserializer 定义如何将JSON转换回类型T的对象。它只有一个方法deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context),返回一个类型为T的对象。

可以看到,不管是 JsonSerializer 还是 JsonDeserializer,都依赖 JsonElement 类进行序列化/反序列化工作。 JsonElement类是Gson中用以表示Json元素的抽象类,他有4个实现类:JsonObjectJsonArrayJsonPrimitiveJsonNull,分别对应表示Json中可能出现的所有类型:Json对象、数组、原始数据类型和空值。

  • JsonObject: 表示JSON对象,即一组键值对,其中键是字符串,值可以是任意类型的JsonElement。JsonObject提供了添加、删除和访问这些键值对的方法。
  • JsonArray: 表示JSON数组,即一个元素列表,这些元素本身可以是任意类型的JsonElement。JsonArray提供了添加、删除和访问这些元素的方法。
  • JsonPrimitive: 表示JSON的原始数据类型,如字符串、数字、布尔值等。JsonPrimitive封装了这些基本类型的值。
  • JsonNull: 表示JSON的空值。在Gson中,JsonNull是单例,用于表示值为null的情况。

通过操作JsonElement及其子类的实例,开发者可以灵活的构造、遍历和操作JSON数据结构。

TypeAdapter

TypeAdapter是Gson 从2.1版本后提供的、用来同时处理序列化和反序列化的接口。与JsonSerializerJsonDeserializer不同的是,TypeAdapter提供了一个单一的实现点,通过两个方法write(JsonWriter out, T value)read(JsonReader in)来分别处理序列化和反序列化。

  kotlin 复制代码
class GenderAdapter: TypeAdapter<Gender>() {
	// 自定义序列化过程:
	// Gender.UNKNOWN -> null
	// Gender.MALE -> "1"
	// Gender.FEMALE -> "2"
    override fun write(out: JsonWriter, value: Gender) {
        when (value) {
            Gender.UNKNOWN -> out.nullValue()
            Gender.MALE -> out.value("1")
            Gender.FEMALE -> out.value("2")
        }
    }
	// 自定义反序列化过程:
	// null -> Gender.UNKNOWN
	// "1" -> Gender.MALE
	// "2" -> Gender.FEMALE
    override fun read(`in`: JsonReader): Gender {
        return when (`in`.peek()) {
            JsonToken.NULL -> {
                `in`.nextNull()
                Gender.UNKNOWN
            }
            JsonToken.NUMBER -> {
                when(`in`.nextInt()) {
                    1 -> Gender.MALE
                    2 -> Gender.FEMALE
                    else -> Gender.UNKNOWN
                }
            }
            else -> Gender.UNKNOWN
        }
    }
}

TypeAdapter提供了更高的灵活性和控制力,因为它直接操作JsonReader和JsonWriter,这使得它可以更高效地处理JSON,避免了中间JsonElement的创建和解析。这在处理大量数据或需要高性能序列化/反序列化时特别有用。

TypeAdapter 和 JsonSerializer/JsonDeserializer 的区别

从上图TypeAdapterJsonSerializer/JsonDeserializer工作流图可以明显看出来,TypeAdapter直接操作JsonReaderJsonWriter,相对于JsonSerializer/JsonDeserializer避免了中间JsonElement的创建和解析。

  • 使用场景: JsonSerializer和JsonDeserializer通常用于更简单的场景,当你只需要定制某个类型的序列化或反序列化行为时。TypeAdapter用于更复杂或性能敏感的场景,提供了完全控制序列化和反序列化过程的能力。
  • 性能: TypeAdapter通常比JsonSerializer和JsonDeserializer更高效,因为它避免了中间JsonElement的创建和解析。
  • 灵活性: TypeAdapter提供了对序列化和反序列化过程的完全控制,而JsonSerializer和JsonDeserializer则在某种程度上受限于Gson的序列化和反序列化框架。

根据具体需求选择合适的接口是关键。对于大多数简单用途,使用JsonSerializerJsonDeserializer可能就足够了。但对于需要细粒度控制或优化性能的场景,TypeAdapter将是更好的选择。

事实上,Gson 2.x 版本 也会将JsonSerializer和JsonDeserializer转换成TreeTypeAdapter

Gson是如何进行解析工作的?

通过上面的基本使用,我们应该能清晰的感知到,TypeAdapter在Gson的解析工作中担任重要角色。在创建Gson实例时,Gson内置了许多TypeAdapter:

  kotlin 复制代码
#com.google.gson.Gson#Gson()
List<TypeAdapterFactory> factories = new ArrayList<>();

// 内置类型的适配器,不可覆盖
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy));

// 忽略的字段、类型适配器
factories.add(excluder);

// 用户自定义的适配器
factories.addAll(factoriesToBeAdded);

// 平台的基础类型适配器
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
factories.add(TypeAdapters.SHORT_FACTORY);
TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
factories.add(TypeAdapters.newFactory(double.class, Double.class,
										  doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class,
										  floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy));
factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
factories.add(TypeAdapters.newFactory(LazilyParsedNumber.class, TypeAdapters.LAZILY_PARSED_NUMBER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.CURRENCY_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DateTypeAdapter.FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);

if (SqlTypesSupport.SUPPORTS_SQL_TYPES) {
	factories.add(SqlTypesSupport.TIME_FACTORY);
	factories.add(SqlTypesSupport.DATE_FACTORY);
	factories.add(SqlTypesSupport.TIMESTAMP_FACTORY);
}

factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);

// map、集合类型适配器
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
// JavaBean类型适配器
factories.add(new ReflectiveTypeAdapterFactory(
	constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory, reflectionFilters));

抛开用户自定义的适配器不谈,剩下的适配器我们将其大致分为三类:

  • 基础数据类型适配器
  • map、集合等容器类型适配器
  • 枚举类型适配器
  • JavaBean类型适配器

对应Gson解析工作的三种情况,Gson是如何解析基础数据类型的、Gson是如何解析map、集合容器类型的、Gson时如何解析枚举类型的、Gson是如何解析JavaBean类型的。我们一个一个来看。

找到合适的类型适配器

Gson进行解析操作的关键就是找到合适的类型适配器,代码在Gson.getAdapter中,源码如下:

  java 复制代码
#Gson.getAdapter()
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
	// 1.检查传入的TypeToken参数是否为null,若为null则抛出异常。
	Objects.requireNonNull(type, "type must not be null");
	// 2.从类型缓存typeTokenCache中尝试获取该类型的适配器,如果缓存中存在,则直接返回。
	TypeAdapter<?> cached = typeTokenCache.get(type);
	if (cached != null) {
		@SuppressWarnings("unchecked")
		TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
		return adapter;
	}
	// 3.从线程本地缓存threadLocalAdapterResults中获取当前线程的适配器请求记录,如果不存在则创建一个新的HashMap,并将其设置到线程本地缓存中。
	Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
	boolean isInitialAdapterRequest = false;
	if (threadCalls == null) {
		threadCalls = new HashMap<>();
		threadLocalAdapterResults.set(threadCalls);
		isInitialAdapterRequest = true;
	} else {
		// 在线程本地缓存中查找当前类型是否有适配器,如果存在则直接返回该适配器。
		@SuppressWarnings("unchecked")
		TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
		if (ongoingCall != null) {
			return ongoingCall;
		}
	}

	TypeAdapter<T> candidate = null;
	try {
		// 4.创建一个FutureTypeAdapter对象,并将其设置为当前类型的适配器请求记录,存入threadLocalAdapterResults
		FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
		threadCalls.put(type, call);

		for (TypeAdapterFactory factory : factories) {
			// 5.遍历TypeAdapterFactory工厂,尝试为当前类型创建适配器,若成功创建,则将该适配器设置给FutureTypeAdapter对象,并替换线程本地缓存中的适配器请求记录。
			candidate = factory.create(this, type);
			if (candidate != null) {
				call.setDelegate(candidate);
				threadCalls.put(type, candidate);
				break;
			}
		}
	} finally {
		if (isInitialAdapterRequest) {
			threadLocalAdapterResults.remove();
		}
	}

	if (candidate == null) {
		// 6.没有找到能够创建适配器的工厂,则抛出IllegalArgumentException异常。
		throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
	}

	if (isInitialAdapterRequest) {
		// 7.当前线程首次请求适配器,则将线程本地缓存中的适配器发布到全局缓存typeTokenCache中。
		typeTokenCache.putAll(threadCalls);
	}
	return candidate;
}

本质上就是通过传入的TypeToken参数来确定需要获取的类型适配器TypeAdapter,只不过加入了缓存相关的逻辑,通过缓存来提高获取效率。

tips:当多个线程并发调用getAdapter()方法请求同一类型的适配器时,此方法可能会返回不同的TypeAdapter实例。如果对应的TypeAdapter的实现是无状态的,这就不会有什么问题,反之就会带来意想不到的问题。谨记‼️

找到合适的适配器后,Gson会通过适配器来进行具体的序列化/反序列化操作。

Gson解析基础数据类型

Gson内置了平台的基础类型适配器,我们以boolean类型为例,来看看Gson是怎么对boolean类型进行序列化/反序列化工作的。boolean类型对应的适配器是TypeAdapters.Boolean,相关代码如下:

  java 复制代码
public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() {
	@Override
	public Boolean read(JsonReader in) throws IOException {
		JsonToken peek = in.peek();
		if (peek == JsonToken.NULL) {
			in.nextNull();
			return null;
		} else if (peek == JsonToken.STRING) {
			// GSON 1.7版本后支持将String解析成boolean类型
			return Boolean.parseBoolean(in.nextString());
		}
		return in.nextBoolean();
	}
	@Override
	public void write(JsonWriter out, Boolean value) throws IOException {
		out.value(value);
	}
};

Gson解析map和集合

Gson通过CollectionTypeAdapterFactory工厂来获取集合类型的适配器并进行序列化/反序列化操作。

  java 复制代码
#CollectionTypeAdapterFactory.java
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
	Type type = typeToken.getType();

	Class<? super T> rawType = typeToken.getRawType();
	if (!Collection.class.isAssignableFrom(rawType)) {
		// 判断传入类型是否为集合类型,如果不是则返回null
		return null;
	}

	// 获取集合元素的类型
	Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
	// 使用gson.getAdapter()方法获取该元素类型的适配器。
	TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
	ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
	// 创建并返回对应的TypeAdapter
	@SuppressWarnings({"unchecked", "rawtypes"})
	TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
	return result;
}

private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
	private final TypeAdapter<E> elementTypeAdapter;
	private final ObjectConstructor<? extends Collection<E>> constructor;

	public Adapter(Gson context, Type elementType,
				   TypeAdapter<E> elementTypeAdapter,
				   ObjectConstructor<? extends Collection<E>> constructor) {
		this.elementTypeAdapter =
		new TypeAdapterRuntimeTypeWrapper<>(context, elementTypeAdapter, elementType);
		this.constructor = constructor;
	}

	@Override public Collection<E> read(JsonReader in) throws IOException {
		if (in.peek() == JsonToken.NULL) {
			in.nextNull();
			return null;
		}
		// 通过constructor.construct()创建集合实例
		Collection<E> collection = constructor.construct();
		// 消费json字符串中的'['
		in.beginArray();
		while (in.hasNext()) {
			// 调用元素TypeAdapter.read(in)生成元素实例
			E instance = elementTypeAdapter.read(in);
			// 加入集合
			collection.add(instance);
		}
		// 消费json字符串中的']'
		in.endArray();
		// 返回集合实例
		return collection;
	}

	@Override public void write(JsonWriter out, Collection<E> collection) throws IOException {
		if (collection == null) {
			out.nullValue();
			return;
		}
		// 生成json字符串中的'['
		out.beginArray();
		// 遍历集合,调用子元素的TypeAdapter.write序列化子元素
		for (E element : collection) {
			elementTypeAdapter.write(out, element);
		}
		// 生成json字符串中的']'
		out.endArray();
	}
}

解析Map类型的过程和集合类似,只不过Map类型需要维护 Key 和 Value 两个TypeAdapter来进行序列化和反序列化操作,此处不再赘述,感兴趣的童鞋可以自行查看源码。

Gson解析枚举类型

Gson在处理枚举类型时,默认使用内置的EnumTypeAdapter。EnumTypeAdapter的工作原理基于枚举值的名称,而不是其序数或任何其他属性。代码如下:

  java 复制代码
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
	private final Map<String, T> nameToConstant = new HashMap<>();
	private final Map<String, T> stringToConstant = new HashMap<>();
	private final Map<T, String> constantToName = new HashMap<>();

	public EnumTypeAdapter(final Class<T> classOfT) {
		try {
			// 使用反射查找枚举常量,以解决混淆类名称的不匹配问题
			// 通过访问控制器以特权上下文运行代码,并获取类的声明字段,筛选出枚举常量字段
			Field[] constantFields = AccessController.doPrivileged(new PrivilegedAction<Field[]>() {
				@Override public Field[] run() {
					Field[] fields = classOfT.getDeclaredFields();
					ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length);
					for (Field f : fields) {
						if (f.isEnumConstant()) {
							constantFieldsList.add(f);
						}
					}

					Field[] constantFields = constantFieldsList.toArray(new Field[0]);
					// 将字段设置为可访问
					AccessibleObject.setAccessible(constantFields, true);
					return constantFields;
				}
			});
			// 遍历枚举常量字段,并构三个关键的map结构表:
			// 1.名称 -> 枚举常量 表 nameToConstant
			// 1.字符串 -> 枚举常量 表 stringToConstant
			// 3.枚举常量 -> 名称 表 constantToName
			for (Field constantField : constantFields) {
				@SuppressWarnings("unchecked")
				T constant = (T)(constantField.get(null));
				String name = constant.name();
				String toStringVal = constant.toString();

				SerializedName annotation = constantField.getAnnotation(SerializedName.class);
				if (annotation != null) {
					name = annotation.value();
					for (String alternate : annotation.alternate()) {
						// 字段上有@SerializedName注解,则使用注解的值作为名称
						nameToConstant.put(alternate, constant);
					}
				}
			
				nameToConstant.put(name, constant);
				stringToConstant.put(toStringVal, constant);
				constantToName.put(constant, name);
			}
		} catch (IllegalAccessException e) {
			throw new AssertionError(e);
		}
	}
	@Override public T read(JsonReader in) throws IOException {
		if (in.peek() == JsonToken.NULL) {
			in.nextNull();
			return null;
		}
		String key = in.nextString();
		// 根据名称获取枚举常量
		T constant = nameToConstant.get(key);
		return (constant == null) ? stringToConstant.get(key) : constant;
	}

	@Override public void write(JsonWriter out, T value) throws IOException {
		// 根据常量获取名称
		out.value(value == null ? null : constantToName.get(value));
	}
}

Gson解析Java Bean

Gson中,Java Bean类型的TypeAdapter都是由ReflectiveTypeAdapterFactory工厂创建的。我们来看创建TypeAdapter的方法:

  java 复制代码
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
    Class<? super T> raw = type.getRawType();

	// Object类型,返回null
    if (!Object.class.isAssignableFrom(raw)) {
      return null; // it's a primitive!
    }
	// 根据反射过滤器的配置,判断是否允许使用反射来访问该类型字段
    FilterResult filterResult =
        ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
    if (filterResult == FilterResult.BLOCK_ALL) {
	  // 不允许,抛出 JsonIOException 异常
      throw new JsonIOException(
          "ReflectionAccessFilter does not permit using reflection for " + raw
              + ". Register a TypeAdapter for this type or adjust the access filter.");
    }
    boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;

	// 如果为Java记录(Record)类型,使用RecordAdapter来处理解析工作
    if (ReflectionHelper.isRecord(raw)) {
      @SuppressWarnings("unchecked")
      TypeAdapter<T> adapter = (TypeAdapter<T>) new RecordAdapter<>(raw,
          getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
      return adapter;
    }
	// 根据该类型的构造函数和字段信息,创建FieldReflectionAdapter实例,并返回该实例作为TypeAdapter的实现
    ObjectConstructor<T> constructor = constructorConstructor.get(type);
    return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
  }

这段代码关键点有以下几点:

  • 根据传入的类型是否为Java记录类型返回不同的TypeAdapter。Java记录(Record)类型,使用RecordAdapter来处理解析工作;反之使用FieldReflectionAdapter处理。
  • 无论是RecordAdapter还是FieldReflectionAdapter,都会通过getBoundFields()方法生成一个用以表示字段名和绑定字段(serializeName - boundFiled)的映射关系的Map对象。

Java 记录类型是Java 14中引入的一个预览特性,并在Java 16中成为正式特性。记录提供了一种简洁的方式来声明只包含数据的不可变类。其特性类似于 kotlin 中的 data class

然后在TypeAdapter中借助这个map对象进行解析工作:

  java 复制代码
public static abstract class Adapter<T, A> extends TypeAdapter<T> {
	final Map<String, BoundField> boundFields;

	Adapter(Map<String, BoundField> boundFields) {
		this.boundFields = boundFields;
	}

	@Override
	public void write(JsonWriter out, T value) throws IOException {
		// value为null,输出null
		if (value == null) {
			out.nullValue();
			return;
		}
		// 生成json字符串"{"
		out.beginObject();
		try {
			// 遍历boundFields中的所有字段,调用boundField.write()方法将字段的值序列化为JSON格式并输出
			for (BoundField boundField : boundFields.values()) {
				boundField.write(out, value);
			}
		} catch (IllegalAccessException e) {
			throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
		}
		// 生成json字符串"}"
		out.endObject();
	}

	@Override
	public T read(JsonReader in) throws IOException {
		// null 值校验
		if (in.peek() == JsonToken.NULL) {
			in.nextNull();
			return null;
		}
		// 创建一个accumulator对象来存储字段的值
		A accumulator = createAccumulator();

		try {
			// 消费json字符串"{"
			in.beginObject();
			// 遍历JSON的所有字段,若字段存在于boundFields中且标记为可反序列化
			// 则调用readField()方法将JSON字段的值反序列化为对应的字段值,并存储到accumulator中
			while (in.hasNext()) {
				String name = in.nextName();
				BoundField field = boundFields.get(name);
				if (field == null || !field.deserialized) {
					in.skipValue();
				} else {
					readField(accumulator, in, field);
				}
			}
		} catch (IllegalStateException e) {
			throw new JsonSyntaxException(e);
		} catch (IllegalAccessException e) {
			throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
		}
		// 消费json字符串"}"
		in.endObject();
		// 将accumulator转换为目标对象的实例并返回
		return finalize(accumulator);
	}

	/** 用于创建用于存储字段值的中间对象 */
	abstract A createAccumulator();
	/**
     * 用于将JSON字段的值反序列化为对应的字段值,并存储到中间对象中
     */
	abstract void readField(A accumulator, JsonReader in, BoundField field)
	throws IllegalAccessException, IOException;
	/** 用于将中间对象转换为目标对象的实例 */
	abstract T finalize(A accumulator);
}

Gson如何创建对象?

与Gson解析一致,Gson创建也因对象类型的不同,分为4种情况:

  • 基础类型、以及基础类型的包装类型等由Gson提供的TypeAdapter通过 new 关键字创建;
  • 枚举类型在EnumTypeAdapter中只是通过枚举名称切换不同的枚举常量,不涉及对象的创建;
  • 集合和map等容器类型通过Gson内置的对象创建工厂,调用 new 关键字进行创建;
  • Java Bean对象的创建比较复杂,分为3种情况,优先级由上到下依次降低:
    • 开发者定义了对象创建工厂InstanceCreator,则使用该工厂创建;
    • 存在默认的无参构造函数,通过反射构造函数创建;
    • 使用Unsafe API 创建。
  java 复制代码
#ConstructorConstructor.java
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
	// 1.获取TypeToken对应的类型(Type)和原始类型(Class<? super T>)
	final Type type = typeToken.getType();
	Class<? super T> rawType = typeToken.getRawType();
	// 2.从instanceCreators中根据类型(Type)获取对应的实例创建器(InstanceCreator)
	//   如果存在,则创建并返回一个新的对象构造器,该构造器使用该实例创建器来创建对象
	final InstanceCreator<T> typeCreator = (InstanceCreator)this.instanceCreators.get(type);
	if (typeCreator != null) {
		return new ObjectConstructor<T>() {
			public T construct() {
				return typeCreator.createInstance(type);
			}
		};
	} else {
		// 3.如果步骤2中没有获取到实例创建器,则尝试根据原始类型(rawType)从instanceCreators中获取实例创建器
		// 如果存在,则创建并返回一个新的对象构造器,该构造器使用该实例创建器来创建对象。
		final InstanceCreator<T> rawTypeCreator = (InstanceCreator)this.instanceCreators.get(rawType);
		if (rawTypeCreator != null) {
			return new ObjectConstructor<T>() {
				public T construct() {
					return rawTypeCreator.createInstance(type);
				}
			};
		} else {
			// 4.如果步骤2和步骤3都没有获取到实例创建器,则尝试调用newDefaultConstructor方法创建一个默认构造器,如果存在,则返回该构造器(无参构造函数)
			ObjectConstructor<T> defaultConstructor = this.newDefaultConstructor(rawType);
			if (defaultConstructor != null) {
				return defaultConstructor;
			} else {
				// 5.如果步骤4中没有创建到默认构造器,则尝试调用newDefaultImplementationConstructor方法创建一个默认实现构造器,如果存在,则返回该构造器(容器类型)
				ObjectConstructor<T> defaultImplementation = this.newDefaultImplementationConstructor(type, rawType);
				// 6.如果步骤5中没有创建到默认实现构造器,则尝试调用newUnsafeAllocator方法创建一个不安全的分配器构造器,最后返回该构造器(Unsafe)
				return defaultImplementation != null ? defaultImplementation : this.newUnsafeAllocator(type, rawType);
			}
		}
	}
}

使用Gson需要注意⚠

通过上文我们分析可知,当Java类未提供默认的无参构造函数时,Gson会使用 Unsafe API 来创建对象,这种创建对象的方式不会调用构造函数,因此会导致以下几个可能的问题:

  • 默认值丢失;
  • Kotlin 非空类型失效;
  • 初始化块可能不会正常执行;

关于更优雅的使用Gson的一些经验之谈

正确的配置Gson

正确配置Gson实例对于确保JSON的序列化和反序列化过程满足应用程序的需求至关重要。不恰当的配置可能导致数据丢失、格式错误或性能低下。下面几条是本人的经验之谈:

注意null值

默认情况下,Gson不会序列化值为null的字段。这可能会导致生成的JSON字符串缺少某些字段,特别是在与严格依赖JSON结构的外部系统交互时可能会出现问题。可以通过在创建Gson实例时使用GsonBuilder并调用serializeNulls()方法来改变这一行为。

不区分大小写的枚举反序列化

在反序列化枚举类型时,Gson默认不区分大小写,这可能会导致一些意外行为。如果JSON字符串中的枚举值和Java枚举常量在大小写上不一致,Gson仍然会将其成功反序列化,这可能不是所有场景下都期望的行为。

合理设置FieldNamingPolicy

使用FieldNamingPolicy来适应服务端不同的字段命名风格

合理定义字段类型

很多时候,服务端往往会通过String类型或者数字类型返回表示枚举的含义,这个时候我们如果按服务端返回类型定义字段就会对代码的可读性造成影响。例如电商app的商品类我们定义如下:

  java 复制代码
data class Good(
    val id: Int,
    val name: String,
    val price: Double,
    val type: String,
) {
    companion object {
        public const val GOOD_TYPE_FRESH = "fresh"
        public const val GOOD_TYPE_BOOK = "book"
        public const val GOOD_TYPE_FOOD = "food"
    }
}

其中type表示商品类型,我们按服务端返回类型将其定义成String类型,type可能有三个值,在使用type字段时,我们往往不知道这个值包含哪几种情况,需要跳转Good类查看代码才能了解到,如果我们将type直接定义为枚举类型呢?

  java 复制代码
data class Good(
    val id: Int,
    val name: String,
    val price: Double,
    val type: GoodType,
)
enum class GoodType {
    @SerializedName("fresh")FRESH,
    @SerializedName("book")BOOK,
    @SerializedName("food")FOOD,
}

两种代码的可读性就有了明显的差距。所以,合理定义字段类型很重要。

巧用TypeAdapter

结合自身产品服务端返回字段的规范合理使用TypeAdapter

服务端常常使用1表示true,0或者其他数字表示false。对于boolean类型,Gson默认是没有这样的解析规则的,所以我们往往只能将字段定义成int类型,再对getter方法做处理:

  kotlin 复制代码
data class Good(
	val id: Int,
	val name: String,
	val price: Double,
	val type: GoodType,
	// 是否为热销商品,1代表热销商品
	val isHot: Int,
) {
	fun isHot(): Boolean {
		return isHot == 1
	}
}

如果服务端对于字段的返回规范就是1表示true其余表示false,那客户端项目中少不了类似的处理。这个时候我们不妨自定义一个TypeAdapter

  java 复制代码
class BooleanTypeAdapter: TypeAdapter<Boolean>() {
    override fun write(out: JsonWriter, value: Boolean) {
        out.value(value)
    }

    override fun read(`in`: JsonReader): Boolean? {
        return when (`in`.peek()) {
            JsonToken.NULL -> {
                `in`.nextNull()
                null
            }

            JsonToken.STRING -> {
                java.lang.Boolean.parseBoolean(`in`.nextString())
            }
			// 对数字类型做解析,1为true,其余为false
            JsonToken.NUMBER -> {
                `in`.nextInt() == 1
            }

            else -> `in`.nextBoolean()
        }
    }
}

然后在配置Gson时全局应用下这个TypeAdapter:

  kotlin 复制代码
val gson = GsonBuilder()
	.registerTypeAdapter(Boolean::class.java, BooleanTypeAdapter())
	.create()

这样就可以使用boolean值定义isHot字段了:

  kotlin 复制代码
data class Good(
	val id: Int,
	val name: String,
	val price: Double,
	val type: GoodType,
	val isHot: Boolean,
)

结合平台特性合理使用TypeAdapter

对于Android开发者,我们往往需要为View设置颜色相关的属性,这些颜色有时是通过服务端返回字段控制的,如换肤相关的背景色,默认情况下,我们只能这么处理:

  kotlin 复制代码
data class Good(
	val id: Int,
	val name: String,
	val price: Double,
	val type: GoodType,
	val isHot: Boolean,
	val bgColor: String,
)

// 使用时,需要将颜色字符串通过Color.parseColor方法解析成Android平台的色值Int类型I
var bgColor: Int
try {
	bgColor = Color.parseColor(good.bgColor)
} catch (e: Exception) {
	bgColor = Color.WHITE
}
view.setBackgroundColor(bgColor)

这个时候自定义TypeAdapter又可以派上用场了:

  kotlin 复制代码
class ColorTypeAdapter : TypeAdapter<Int>() {
	override fun write(out: JsonWriter, value: Int?) {
		if (value == null) {
			out.nullValue()
		} else {
			// 将Int颜色值转换为16进制字符串
			val colorStr = "#${Integer.toHexString(value).toUpperCase()}"
			out.value(colorStr)
		}
	}

	override fun read(`in`: JsonReader): Int? {
		if (`in`.peek() == JsonToken.NULL) {
			`in`.nextNull()
			return null
		}
		// 从JSON读取字符串并转换为Int颜色值
		val colorStr = `in`.nextString()
		return try {
			// 使用Android的Color类来解析16进制颜色字符串
			Color.parseColor(colorStr)
		} catch (e: IllegalArgumentException) {
			null
		}
	}
}

然后对bgColor字段应用ColorTypeAdapter

  kotlin 复制代码
data class Good(
	val id: Int,
	val name: String,
	val price: Double,
	val type: GoodType,
	val isHot: Boolean,
	@JsonAdapter(ColorTypeAdapter::class)
	val bgColor: String,
)
// 使用时,直接使用bgColor,无需手动解析
view.setBackgroundColor(bgColor)

所以,使用Gson时,结合平台特性,合理自定义TypeAdapte也能大大提升开发效率。


作者:沈剑心
链接:https://juejin.cn/post/7355800792073469992

标签:序列化,null,add,优雅,Json,factories,Gson,TypeAdapter
From: https://www.cnblogs.com/terrorists/p/18142210

相关文章

  • Fastjson反序列化漏洞
    Fastjson简介Fastjson是一个Java库,可以实现json和对象之间的转换。将数据与对象进行转化,这个操作涉及到了反序列化。与原生的Java反序列化不同,FastJson反序列化并未使用readObject方法,而是自定义了反序列化的过程。通过在反序列化的过程中自动调用类属性的setter方法......
  • Cysimdjson:地球上最快的 JSON 解析器
    处理简单的少量数据,对速度是无感的,但如果要处理大量数据,哪怕每次几十毫秒的差异,最终也会差异巨大。比如,你要为客户清洗一遍企业系统数据中,一堆之前留下的庞大的JSON文件。如果你打算用Python自带的JSON模块,那就调整好心态,备足咖啡,享受煎熬吧。但如果有人告诉你,有比Py......
  • Python 如何优雅的操作 PyMySQL
    一、PyMysql在使用Python操作MySQL数据过的过程中,基本的增删改查操作如何更加高效优雅的执行。这里将以PyMySQL为例,介绍一下如何使用Python操作数据库。Python对MySQL数据库进行操作,基本思路是先连接数据库Connection对象,建立游标Cursor对象,然后执行SQL语句对数据库进行操作......
  • python读写json文件
    1.新建json文件打开记事本,重命名为.json后缀使用的样例如下,注意看json文件格式:{"server":{"host":"example.com","port":443,"protocol":"https"},"authentication":{......
  • ETL工具中JSON格式的转换方式
    JSON的用处JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,其设计初衷是为了提升网络应用中数据的传输效率及简化数据结构的解析过程。自其诞生以来,JSON 已成为Web开发乃至众多软件开发领域中不可或缺的一部分,以其高效、灵活、易读易写的特性,成为了数据交换和存储......
  • 如何写出优雅的代码,程序员都要了解的开闭原则
    开闭原则(Open-ClosedPrinciple,OCP)是面向对象编程中的重要原则之一。它指出软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。要写出优雅的代码,可以考虑以下几点:良好的结构和设计:合理划分模块,保持代码的清晰性和可读性。单一职责原则:每个类或函数只负责一项特定的功能。......
  • rapidjson
    一、简介RapidJSON是腾讯开源的一个高效的C++JSON解析器及生成器,它是只有头文件的C++库。RapidJSON是跨平台的,支持Windows、Linux、MacOSX及iOS、Android。writer和prettywriter都是将JSON数据打包为字符串的方法。官网:https://rapidjson.org/zh-cn/index.html1.1write和pr......
  • python教程6.3-json序列化
    序列化:dumps,编码,将python类型转成json对象反序列化:loads,解码,将json对象转成python对象pickle模块提供了四个功能:dumps、loads、dump、load(前2个操作变量,后2个操作文件)jsonjson模块也提供了四个功能:dumps、dump、loads、load,⽤法跟pickle⼀致。(前2个操作变量,后2个操作文件)......
  • labelme标注后的json文件去掉某个类别的标签并生成新的json文件
    以去掉secondary_particle标签为例点击查看代码importjsonimportos#去除标注图像中的一次颗粒标签defremove_specific_labels(json_file):withopen(json_file,'r',encoding='utf-8',errors='ignore')asf:data=json.load(f)if"s......
  • 华为云开发者桌面全新发布CodeArts IDE for Python,极致优雅云原生开发体验
    本文分享自华为云社区《华为云发布CodeArtsIDEforPython,极致优雅云原生开发体验》,作者:华为云头条。近日,华为云正式发布CodeArtsIDEforPython,这是一款内置华为自主创新的Python语言服务,提供智能编程、灵活调试能力的可扩展桌面开发工具,为华为云开发者提供卓越Python编码体验......