diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FieldValue.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FieldValue.java
index d11df4b956af..7e4e019eb726 100644
--- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FieldValue.java
+++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FieldValue.java
@@ -378,13 +378,47 @@ public static FieldValue of(Attribute attribute, Object value, Boolean useInt64T
return new FieldValue(attribute, value, useInt64Timestamps);
}
+ // `google-http-client`'s canonical sentinel for JSON `null` values in `Object`-typed fields
+ // (such as `TableCell.v`). See `com.google.api.client.util.Data.nullOf(Object.class)`.
+ private static final Object NULL_CELL_SENTINEL = Data.nullOf(Object.class);
+
static FieldValue fromPb(Object cellPb) {
return fromPb(cellPb, null, false);
}
+ /**
+ * Converts a raw or wrapped BigQuery REST v2 cell representation into a {@link FieldValue}.
+ *
+ *
Accepts both unwrapped cell values produced by {@code HttpBigQueryRpc}'s streaming row
+ * parser ({@code String}, {@code Data.nullOf(Object.class)}, {@code List} for {@code REPEATED},
+ * or {@code Map("f", List)} for {@code RECORD}) and wrapped {@link
+ * com.google.api.services.bigquery.model.TableCell} or {@code Map("v", ...)} objects (used by
+ * unit tests and custom {@code BigQueryRpc} implementations).
+ *
+ *
See the BigQuery REST v2 Discovery specification for {@code TableRow} and {@code TableCell}:
+ *
+ * https://bigquery.googleapis.com/$discovery/rest?version=v2
+ */
@SuppressWarnings("unchecked")
static FieldValue fromPb(Object cellPb, Field recordSchema, Boolean useInt64Timestamps) {
- if (Data.isNull(cellPb)) {
+ // Fast-path for TableCell instances (constructed in unit tests or custom BigQueryRpc
+ // implementations). TableCell extends GenericJson -> GenericData, which implements
+ // Map. Unwrapping `getV()` upfront avoids falling into `cellPb instanceof Map`
+ // below, where `containsKey("f")` and `get("v")` trigger reflective ClassInfo/FieldInfo
+ // lookups on GenericData.
+ if (cellPb instanceof com.google.api.services.bigquery.model.TableCell) {
+ cellPb = ((com.google.api.services.bigquery.model.TableCell) cellPb).getV();
+ }
+ // Check `cellPb == null` and `cellPb == NULL_CELL_SENTINEL` before calling
+ // `Data.isNull(cellPb)`:
+ // 1. `Data.isNull(null)` returns `false` (it only matches non-null sentinel instances
+ // registered in `Data.NULL_CACHE`), so unwrapped `null` references must be checked first.
+ // 2. `Data.nullOf(Object.class)` is the singleton sentinel used by google-http-client and
+ // HttpBigQueryRpc for JSON `null` values in `Object`-typed fields (`TableCell.v`). Checking
+ // reference equality (`==`) avoids `Data.isNull`'s `ConcurrentHashMap.get` lookup on every
+ // null cell while still falling back to `Data.isNull` for typed null sentinels such as
+ // `Data.nullOf(String.class)`.
+ if (cellPb == null || cellPb == NULL_CELL_SENTINEL || Data.isNull(cellPb)) {
return FieldValue.of(Attribute.PRIMITIVE, null, useInt64Timestamps);
}
if (cellPb instanceof String) {
@@ -412,8 +446,8 @@ static FieldValue fromPb(Object cellPb, Field recordSchema, Boolean useInt64Time
FieldValueList.fromPb(
(List