探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因

news2025/4/22 23:23:09

文章目录

  • 一、问题背景
  • 二、问题原因
  • 三、问题探析
    • Kotlin空指针校验
    • Gson.fromJson(String json, Class<T> classOfT)
    • TypeToken
    • Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT)
    • TypeAdapter 和 TypeAdapterFactory
    • ReflectiveTypeAdapterFactory
    • RecordAdapter 和 FieldReflectionAdapter
  • 四、解决方法

一、问题背景

在一次开发过程中,由于在 Kotlin 定义的实体类多了一个 json 不存在的 时,即使是对象类型是不可空的对象且指定了默认值,使用 Gson 库解析出来的实体对象中的那个变量是null,导致后面使用的此变量的时候导致出现空指针异常。比如:
实体类对象定义如下

data class Entity(
    /**
     * 存在的元素
     */
    val existParam: String,

    /**
     * 不存在的元素
     */
    val nonExistParam: String = ""
)

nonExistParamjson 结构中不存在的 keyjson 如下

{
    "existParam" : "exist"
}

使用 Gson 进行解析 json

val jsonEntity = Gson().fromJson(json, Entity::class.java)
println("entity = $jsonEntity")

最后得到的输出为:
entity = Entity(existParam=exist, nonExistParam=null)

此时可以发现,nonExistParam 已经被指定为不可空的String 类型,且使用了默认值 "",但解析出来的实体类中nonExistParam=null,如果此时不注意直接使用 nonExistParam,可能引发空指针异常。

二、问题原因

此问题的原因是,Gson 在解析实体类的时候会使用反射构造方法创建对象,在通过反射的方式设置对象的值。因此,如果实体类的成员在json中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)。而在 Kotlin 中,只要在调用实际方法的时候,会触发Kotlin的空校验,从而抛出空指针异常,提早发现问题。但是Gson的反射的方式避开了这个空校验,所以成员的值为 null,直到使用时可能会出现空指针异常

三、问题探析

我们需要探寻 Gson 在解析 json 的时候,究竟发生了什么,导致会出现解析出来的对象出现了 null

Kotlin空指针校验

但是,我们知道Kotlin是对可空非常敏感的,已经指定了成员是不可空的,为什么会把 null 赋值给了不可空成员呢。

我们可以看 Kotlin 的字节码,并反编译成java源码,可以看到最后由Kotlin生成的java源码是怎样的。
在这里插入图片描述
我们可以得到如下的两个方法。

public Entity(@NotNull String existParam, @NotNull String nonExistParam) {
   Intrinsics.checkNotNullParameter(existParam, "existParam");
   Intrinsics.checkNotNullParameter(nonExistParam, "nonExistParam");
   super();
   this.existParam = existParam;
   this.nonExistParam = nonExistParam;
}

// $FF: synthetic method
public Entity(String var1, String var2, int var3, DefaultConstructorMarker var4) {
   if ((var3 & 2) != 0) {
      var2 = "";
   }

   this(var1, var2);
}

第一个即为构造方法,传递了两个参数,且有 Intrinsics.checkNotNullParameter 可空检查。如果这里有空,则会抛出异常。而下一个则是因为对 nonExistParam 的变量设置了默认值生成的构造方法,默认值为 “”
因此,只有正常调用构造方法的时候,才会触发可空的检查。

Gson.fromJson(String json, Class classOfT)

首先,我们使用的方法是 Gson.fromJson(String json, Class<T> classOfT),这个方法是传进一个 json 的字符串和实体对象的 Class 类型,随后的返回值就是一个实体对象。方法如下:

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
  T object = fromJson(json, TypeToken.get(classOfT));
  return Primitives.wrap(classOfT).cast(object);
}

我们先看 Primitives.wrap(classOfT).cast(object); 这句的作用,点进去看 Primitives.wrap()方法:

/**
 * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise
 * returns {@code type} itself. Idempotent.
 *
 * <pre>
 *     wrap(int.class) == Integer.class
 *     wrap(Integer.class) == Integer.class
 *     wrap(String.class) == String.class
 * </pre>
 */
@SuppressWarnings({"unchecked", "MissingBraces"})
public static <T> Class<T> wrap(Class<T> type) {
  if (type == int.class) return (Class<T>) Integer.class;
  if (type == float.class) return (Class<T>) Float.class;
  if (type == byte.class) return (Class<T>) Byte.class;
  if (type == double.class) return (Class<T>) Double.class;
  if (type == long.class) return (Class<T>) Long.class;
  if (type == char.class) return (Class<T>) Character.class;
  if (type == boolean.class) return (Class<T>) Boolean.class;
  if (type == short.class) return (Class<T>) Short.class;
  if (type == void.class) return (Class<T>) Void.class;
  return type;
}

从代码中可以看出,这个方法的作用就是将基本数据类型转换成包装类,即将 int 转换成 Integer,将 float 转换成 Float 等。如果非基本数据类,则直接返回类的本身。而随后接的 .cast(object) 则是强制数据类型转换的的Class类接口,即是 (T) object

因此最后一句的作用只是用来强制转换对象的,与解析 json 无关。我们回到第一句 T object = fromJson(json, TypeToken.get(classOfT));,这句代码调用了 Gson.fromJson(String json, TypeToken<T> typeOfT),并使用 TypeToken 包装了 class

TypeToken

我们先看 TypeToken 的官方文档解释:

Represents a generic type T. Java doesn’t yet provide a way to represent generic types, so this class does. Forces clients to create a subclass of this class which enables retrieval the type information even at runtime.

这是一个代表泛型T(generic type T)的类,在 Java 运行时会进行泛型擦除,因此在运行过程中是无法拿到泛型的准确类型,因此 TypeToken 被创建出来,可以在运行时创建基于此类的子类并拿到泛型的信息。也即这个类通过包装泛型类,提供了在运行时获取泛型对象的类信息的能力。

Gson.fromJson(JsonReader reader, TypeToken typeOfT)

Gson.fromJson(String json, TypeToken<T> typeOfT) 方法开始,层次往下只是将 String 或 其他类型的来源封装成 JsonReader类,代码如下:

public <T> T fromJson(String json, TypeToken<T> typeOfT) throws JsonSyntaxException {
  if (json == null) {
    return null;
  }
  StringReader reader = new StringReader(json);
  return fromJson(reader, typeOfT);
}

public <T> T fromJson(Reader json, TypeToken<T> typeOfT)
    throws JsonIOException, JsonSyntaxException {
  JsonReader jsonReader = newJsonReader(json);
  T object = fromJson(jsonReader, typeOfT);
  assertFullConsumption(object, jsonReader);
  return object;
}

首先使用 StringReader 包装 json 字符串,随后使用 JsonReader 包装 StringReader,随后再调用 Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT) 进行解析 json,得到 <T> 对象。因此我们来看 Gson.fromJson(String json, TypeToken<T> typeOfT) 方法。

public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT)
    throws JsonIOException, JsonSyntaxException {
  boolean isEmpty = true;
  Strictness oldStrictness = reader.getStrictness();

  if (this.strictness != null) {
    reader.setStrictness(this.strictness);
  } else if (reader.getStrictness() == Strictness.LEGACY_STRICT) {
    // For backward compatibility change to LENIENT if reader has default strictness LEGACY_STRICT
    reader.setStrictness(Strictness.LENIENT);
  }

  try {
    JsonToken unused = reader.peek();
    isEmpty = false;
    TypeAdapter<T> typeAdapter = getAdapter(typeOfT);
    return typeAdapter.read(reader);
  } catch (EOFException e) {
    /*
     * For compatibility with JSON 1.5 and earlier, we return null for empty
     * documents instead of throwing.
     */
    if (isEmpty) {
      return null;
    }
    throw new JsonSyntaxException(e);
  } catch (IllegalStateException e) {
    throw new JsonSyntaxException(e);
  } catch (IOException e) {
    // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
    throw new JsonSyntaxException(e);
  } catch (AssertionError e) {
    throw new AssertionError(
        "AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
  } finally {
    reader.setStrictness(oldStrictness);
  }
}

这个方法的一开始是将Gson的 Strictness设置给 JsonReader。随后再获取 类型的 TypeAdapter,使用TypeAdapterread.read(JsonReader in),进行解析 json得到实体对象。

TypeAdapter 和 TypeAdapterFactory

TypeAdapter 是一个抽象类,其有两个抽象方法

/**
 * Writes one JSON value (an array, object, string, number, boolean or null) for {@code value}.
 *
 * @param value the Java object to write. May be null.
 */
public abstract void write(JsonWriter out, T value) throws IOException;

/**
 * Reads one JSON value (an array, object, string, number, boolean or null) and converts it to a
 * Java object. Returns the converted object.
 *
 * @return the converted Java object. May be {@code null}.
 */
public abstract T read(JsonReader in) throws IOException;

也就是 write() 方法定义如何把 实体对象 转换成 json字符串 的实现,和 read() 方法定义如何把 json字符串 转换成 实体对象 的实现。默认已经有部分实现了 Java 常用类的转换方式,如基础数据类 int,float,boolean等 和 map 、set、list 提供转换方式。

TypeAdapterFactory是一个接口,只有一个 creat() 的方法

/**
 * Returns a type adapter for {@code type}, or null if this factory doesn't support {@code type}.
 */
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);

此接口将支持的类型 type 返回一个 TypeAdapter,支持的 type 可以是多种类型。如果不支持的话就返回null。因此 TypeAdapterFactoryTypeAdapter 互相配合,可以生成解析和生成json的具体实现方法。

通过一个类型获取 TypeAdapterGson.getAdapter() 方法如下

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
  Objects.requireNonNull(type, "type must not be null");
  TypeAdapter<?> cached = typeTokenCache.get(type);
  if (cached != null) {
    @SuppressWarnings("unchecked")
    TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
    return adapter;
  }

  
  Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
  boolean isInitialAdapterRequest = false;
  if (threadCalls == null) {
    threadCalls = new HashMap<>();
    threadLocalAdapterResults.set(threadCalls);
    isInitialAdapterRequest = true;
  } else {
    // the key and value type parameters always agree
    @SuppressWarnings("unchecked")
    TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }
  }

  TypeAdapter<T> candidate = null;
  try {
    FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
    threadCalls.put(type, call);

    for (TypeAdapterFactory factory : factories) {
      candidate = factory.create(this, type);
      if (candidate != null) {
        call.setDelegate(candidate);
        // Replace future adapter with actual adapter
        threadCalls.put(type, candidate);
        break;
      }
    }
  } finally {
    if (isInitialAdapterRequest) {
      threadLocalAdapterResults.remove();
    }
  }

  if (candidate == null) {
    throw new IllegalArgumentException(
        "GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
  }

  if (isInitialAdapterRequest) {
    /*
     * Publish resolved adapters to all threads
     * Can only do this for the initial request because cyclic dependency TypeA -> TypeB -> TypeA
     * would otherwise publish adapter for TypeB which uses not yet resolved adapter for TypeA
     * See https://github.com/google/gson/issues/625
     */
    typeTokenCache.putAll(threadCalls);
  }
  return candidate;
}

首先,从缓存Map 的 typeTokenCache 中取出 TypeAdapter,如果有的话,则直接返回此 TypeAdapter 进行使用。

Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {
  @SuppressWarnings("unchecked")
  TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
  return adapter;
}

随后从 ThreadLocal 中去取出 TypeAdapter,如果有的话,则直接返回此 TypeAdapter 进行使用。如果没有当前线程的 threadCalls Map,则直接创建新的threadCalls

Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {
  threadCalls = new HashMap<>();
  threadLocalAdapterResults.set(threadCalls);
  isInitialAdapterRequest = true;
} else {
  // the key and value type parameters always agree
  @SuppressWarnings("unchecked")
  TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
  if (ongoingCall != null) {
    return ongoingCall;
  }
}

随后遍历 Gson 对象的 TypeAdapterFactory List,如果是适合的对象,即通过 TypeAdapterFactory.create() 方法可以创建 TypeAdapter,则直接返回此对象。如果找不到,则会抛出异常。

TypeAdapter<T> candidate = null;
try {
  FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
  threadCalls.put(type, call);

  for (TypeAdapterFactory factory : factories) {
    candidate = factory.create(this, type);
    if (candidate != null) {
      call.setDelegate(candidate);
      // Replace future adapter with actual adapter
      threadCalls.put(type, candidate);
      break;
    }
  }
} finally {
  if (isInitialAdapterRequest) {
    threadLocalAdapterResults.remove();
  }
}

if (candidate == null) {
  throw new IllegalArgumentException(
      "GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
}

因此需要去研究不同类型的 TypeAdapter 的做了什么。

ReflectiveTypeAdapterFactory

Gson 的构造方法中,会将支持的 TypeAdapterFactory 添加进 Gson 类的 fatories 中,有以下语句:

List<TypeAdapterFactory> factories = new ArrayList<>();

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy));

// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);

// users' type adapters
factories.addAll(factoriesToBeAdded);

// type adapters for basic platform types
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));
// Add adapter for LazilyParsedNumber because user can obtain it from Gson and then try to
// serialize it again
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(DefaultDateTypeAdapter.DEFAULT_STYLE_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);

// 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,
     reflectionFilters));

this.factories = Collections.unmodifiableList(factories);

首先我们根据这个列表顺序,结合 for (TypeAdapterFactory factory : factories) 分析得到,对于自己定义的实体类,使用的 TypeAdapterFactoryReflectiveTypeAdapterFactory,即是反射型的 TypeAdapterFactory

我们先来看 ReflectiveTypeAdapterFactory.create() 方法创建 TypeAdapter,这段代码的作用是根据 class的类型生成不同的TypeAdapter

public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
  Class<? super T> raw = type.getRawType();

  if (!Object.class.isAssignableFrom(raw)) {
    return null; // it's a primitive!
  }

  // Don't allow using reflection on anonymous and local classes because synthetic fields for
  // captured enclosing values make this unreliable
  if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {
    // This adapter just serializes and deserializes null, ignoring the actual values
    // This is done for backward compatibility; troubleshooting-wise it might be better to throw
    // exceptions
    return new TypeAdapter<T>() {
      @Override
      public T read(JsonReader in) throws IOException {
        in.skipValue();
        return null;
      }

      @Override
      public void write(JsonWriter out, T value) throws IOException {
        out.nullValue();
      }

      @Override
      public String toString() {
        return "AnonymousOrNonStaticLocalClassAdapter";
      }
    };
  }

  FilterResult filterResult =
      ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
  if (filterResult == FilterResult.BLOCK_ALL) {
    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;

  // If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
  // always be false on JVMs that do not support records.
  if (ReflectionHelper.isRecord(raw)) {
    @SuppressWarnings("unchecked")
    TypeAdapter<T> adapter =
        (TypeAdapter<T>)
            new RecordAdapter<>(
                raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
    return adapter;
  }

  ObjectConstructor<T> constructor = constructorConstructor.get(type);
  return new FieldReflectionAdapter<>(
      constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
}

首先,对于 私有类 、 匿名内部类 、非静态内部类是不支持生成json的,此时会返回 nullTypeAdapter 或者 不生成 jsonTypeAdapter

Class<? super T> raw = type.getRawType();

if (!Object.class.isAssignableFrom(raw)) {
  return null; // it's a primitive!
}

// Don't allow using reflection on anonymous and local classes because synthetic fields for
// captured enclosing values make this unreliable
if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {
  // This adapter just serializes and deserializes null, ignoring the actual values
  // This is done for backward compatibility; troubleshooting-wise it might be better to throw
  // exceptions
  return new TypeAdapter<T>() {
    @Override
    public T read(JsonReader in) throws IOException {
      in.skipValue();
      return null;
    }

    @Override
    public void write(JsonWriter out, T value) throws IOException {
      out.nullValue();
    }

    @Override
    public String toString() {
      return "AnonymousOrNonStaticLocalClassAdapter";
    }
  };
}

如果是 Java 14 之后 Record类,则使用 RecordAdapterTypeAdapter

// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
// always be false on JVMs that do not support records.
if (ReflectionHelper.isRecord(raw)) {
  @SuppressWarnings("unchecked")
  TypeAdapter<T> adapter =
      (TypeAdapter<T>)
          new RecordAdapter<>(
              raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
  return adapter;
}

而如果是普通的类型,则使用 FieldReflectionAdapterTypeAdapter

ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(
    constructor, getBoundFields(gson, type, raw, blockInaccessible, false));

RecordAdapter 和 FieldReflectionAdapter

RecordAdapterFieldReflectionAdapter 都是 Adapter 的子类,其都没有覆写 writeread 的方法,因此我们直接看 Adapter 的的 read 方法。

@Override
public T read(JsonReader in) throws IOException {
  if (in.peek() == JsonToken.NULL) {
    in.nextNull();
    return null;
  }

  A accumulator = createAccumulator();
  Map<String, BoundField> deserializedFields = fieldsData.deserializedFields;

  try {
    in.beginObject();
    while (in.hasNext()) {
      String name = in.nextName();
      BoundField field = deserializedFields.get(name);
      if (field == null) {
        in.skipValue();
      } else {
        readField(accumulator, in, field);
      }
    }
  } catch (IllegalStateException e) {
    throw new JsonSyntaxException(e);
  } catch (IllegalAccessException e) {
    throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
  }
  in.endObject();
  return finalize(accumulator);
}

首先,会通过 A accumulator = createAccumulator(); 方法获取到一个指定类型的对象,从方法中可以看到,其实是调用 constructor.construct(); 反射调用构造方法生成指定类型的对象。

// FieldReflectionAdapter.java
@Override
T createAccumulator() {
  return constructor.construct();
}

// RecordAdapter.java
@Override
Object[] createAccumulator() {
  return constructorArgsDefaults.clone();
}

在初始化的时候,会先调用 getBoundFields() 方法,通过反射的方式,获取指定类型已经声明了的成员。因此通过get 方法,去判断 jsonkey 是否存在,

BoundField field = deserializedFields.get(name);
if (field == null) {
  in.skipValue();
} else {
  readField(accumulator, in, field);
}

可以看 FieldReflectionAdapterreadField 方法 (Kotlin对象未使用Recond

@Override
void readField(T accumulator, JsonReader in, BoundField field)
    throws IllegalAccessException, IOException {
  field.readIntoField(in, accumulator);
}

继续往下看 BoundField.readIntoField()

@Override
void readIntoField(JsonReader reader, Object target)
    throws IOException, IllegalAccessException {
  Object fieldValue = typeAdapter.read(reader);
  if (fieldValue != null || !isPrimitive) {
    if (blockInaccessible) {
      checkAccessible(target, field);
    } else if (isStaticFinalField) {
      // Reflection does not permit setting value of `static final` field, even after calling
      // `setAccessible`
      // Handle this here to avoid causing IllegalAccessException when calling `Field.set`
      String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
      throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
    }
    field.set(target, fieldValue);
  }
}

最后是通过反射的方式,field.set(target, fieldValue);json 中的 value 设置到指定对象中具体的成员中。

因此,如果实体类的成员在json中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)

四、解决方法

Gson 解析 json 的源码中可以得出,由于使用了反射的方式,所以最后生成对象中可能会出现null,尤其是实体类中存在 json 没有的 key ,或者虽然 key 存在时但 value 就是null。因此,在设计json的实体类的时候,需要考虑成员是可空的情况,尽量使用可空类型,避免出现空指针异常。或者使用kotlinx.serialization 进行Kotlin JSON序列化,保证数据的可空安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340395.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

冰川流域提取分析——ArcGIS pro

一、河网提取和流域提取视频详细GIS小熊 || 6分钟学会水文分析—河网提取&#xff08;以宜宾市为例&#xff09;_哔哩哔哩_bilibili 首先你要生成研究区域DEM&#xff0c;然后依次是填洼→流向→流量→栅格计算器→河网分级→栅格河网矢量化&#xff08;得到河网.shp&#xff…

wordpress 垂直越权(CVE=2021-21389)漏洞复现详细教程

关于本地化搭建vulfocus靶场的师傅可以参考我置顶文章 KALI搭建log4j2靶场及漏洞复现全流程-CSDN博客https://blog.csdn.net/2301_78255681/article/details/147286844 描述: BuddyPress 是一个用于构建社区站点的开源 WordPress 插件。在 7.2.1 之前的 5.0.0 版本的 BuddyP…

MySQL 线上大表 DDL 如何避免锁表(pt-online-schema-change)

文章目录 1、锁表问题2、pt-online-schema-change 原理3、pt-online-schema-change 实战3.1、准备数据3.2、安装工具3.3、模拟锁表3.4、解决锁表 1、锁表问题 在系统研发过程中&#xff0c;随着业务需求千变万化&#xff0c;避免不了调整线上MySQL DDL数据表的操作&#xff0c…

剑指offer经典题目(五)

目录 栈相关 二叉树相关 栈相关 题目一&#xff1a;定义栈的数据结构&#xff0c;请在该类型中实现一个能够得到栈中所含最小元素的 min 函数&#xff0c;输入操作时保证 pop、top 和 min 函数操作时&#xff0c;栈中一定有元素。OJ地址 图示如下。 主要思想&#xff1a;我们…

3、排序算法1---按考研大纲做的

一、插入排序 1、直接插入排序 推荐先看这个视频 1.1、原理 第一步&#xff0c;索引0的位置是有序区&#xff08;有序区就是有序的部分&#xff0c;刚开始就只有第一个数据是有序的&#xff09;。第二步&#xff0c;将第2个位置到最后一个位置的元素&#xff0c;依次进行排…

llama-webui docker实现界面部署

1. 启动ollama服务 [nlp server]$ ollama serve 2025/04/21 14:18:23 routes.go:1007: INFO server config env"map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST: OLLAMA_KEEP_ALIVE:24h OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:4 OLLAMA_MAX_…

Linux的Socket开发补充

是listen函数阻塞等待连接&#xff0c;还是accept函数阻塞等待连接&#xff1f; 这两个函数的名字&#xff0c;听起来像listen一直在阻塞监听&#xff0c;有连接了就accept&#xff0c;但其实不是的。 调用listen()后&#xff0c;程序会立即返回&#xff0c;继续执行后续代码&a…

Spring-AOP分析

Spring分析-AOP 1.案例引入 在上一篇文章中&#xff0c;【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】&#xff0c;我们了解到了IOC容器的创建过程&#xff0c;在文末也提到了AOP相关&#xff0c;但是没有作细致分析&#xff0c;这篇文章就结合示例&am…

【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁

目录 Start:什么是Semantic Kernel&#xff1f; 一、Semantic Kernel的本质&#xff1a;AI时代的操作系统内核 1.1 重新定义LLM的应用边界 1.2 技术定位对比 二、SK框架的六大核心组件与技术实现 2.1 内核&#xff08;Kernel&#xff09;&#xff1a;智能任务调度中心 2…

你学会了些什么211201?--http基础知识

概念 HTTP–Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff1b;是一种建立在TCP上的无状态连接&#xff08;短连接&#xff09;。 整个基本的工作流程是&#xff1a;客户端发送一个HTTP请求&#xff08;Request &#xff09;&#xff0c;这个请求说明了客户端…

每天学一个 Linux 命令(29):tail

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/29/index.html tail 命令用于显示文件的末尾内容,默认显示最后 10 行。它常用于实时监控日志文件或查看文件的尾部数据。以下是详细说明和示例: 命令格式 tail [选项] [文件...]常用选项 选项描述-n <NUM> …

【形式化验证基础】活跃属性Liveness Property和安全性质(Safety Property)介绍

文章目录 一、Liveness Property1、概念介绍2、形式化定义二、Safety Property1. 定义回顾2. 核心概念解析3. 为什么强调“有限前缀”4. 示例说明4.1 示例1:交通信号灯系统4.2 示例2:银行账户管理系统5. 实际应用的意义三. 总结一、Liveness Property 1、概念介绍 在系统的…

PI0 Openpi 部署(仅测试虚拟环境)

https://github.com/Physical-Intelligence/openpi/tree/main 我使用4070tisuper, 14900k,完全使用官方默认设置&#xff0c;没有出现其他问题。 目前只对examples/aloha_sim进行测试&#xff0c;使用docker进行部署, 默认使用pi0_aloha_sim模型(但是文档上没找到对应的&…

计算机视觉——利用AI幻觉检测图像是否是生成式算生成的图像

概述 俄罗斯的新研究提出了一种非常规方法&#xff0c;用于检测不真实的AI生成图像——不是通过提高大型视觉-语言模型&#xff08;LVLMs&#xff09;的准确性&#xff0c;而是故意利用它们的幻觉倾向。 这种新方法使用LVLMs提取图像的多个“原子事实”&#xff0c;然后应用自…

FlaskRestfulAPI接口的初步认识

FlaskRestfulAPI 介绍 记录学习 Flask Restful API 开发的过程 项目来源&#xff1a;【Flask Restful API教程-01.Restful API介绍】 我的代码仓库&#xff1a;https://gitee.com/giteechaozhi/flask-restful-api.git 后端API接口实现功能&#xff1a;数据库访问控制&#xf…

CSS预处理工具有哪些?分享主流产品

目前主流的CSS预处理工具包括&#xff1a;Sass、Less、Stylus、PostCSS等。其中&#xff0c;Sass是全球使用最广泛的CSS预处理工具之一&#xff0c;以强大的功能、灵活的扩展性以及完善的社区生态闻名。Sass通过增加变量、嵌套、混合宏&#xff08;mixin&#xff09;等功能&…

AI 速读 SpecReason:让思考又快又准!

在大模型推理的世界里&#xff0c;速度与精度往往难以兼得。但今天要介绍的这篇论文带来了名为SpecReason的创新系统&#xff0c;它打破常规&#xff0c;能让大模型推理既快速又准确&#xff0c;大幅提升性能。想知道它是如何做到的吗&#xff1f;快来一探究竟&#xff01; 论…

Qt通过ODBC和QPSQL两种方式连接PostgreSQL或PolarDB PostgreSQL版

一、概述 以下主要在Windows下验证连接PolarDB PostgreSQL版&#xff08;阿里云兼容 PostgreSQL的PolarDB版本&#xff09;。Linux下类似&#xff0c;ODBC方式则需要配置odbcinst.ini和odbc.ini。 二、代码 以下为完整代码&#xff0c;包含两种方式连接数据库&#xff0c;并…

MobaXterm连接Ubuntu(SSH)

1.查看Ubuntu ip 打开终端,使用指令 ifconfig 由图可知ip地址 2.MobaXterm进行SSH连接 点击session,然后点击ssh,最后输入ubuntu IP地址以及用户名

蓝桥杯2024省A.成绩统计

蓝桥杯2024省A.成绩统计 题目 题目解析与思路 题目要求返回至少要检查多少个人的成绩&#xff0c;才有可能选出k名同学&#xff0c;他们的方差小于一个给定的值 T 二分枚举答案位置&#xff0c;将答案位置以前的数组单独取出并排序&#xff0c;然后用k长滑窗O(1)计算方差 问…