square / square/wire

Unknown enum values in repeated fields cause deserialization crash

Open
#3,508 3 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
4.4k
Forks
627
Avg merge
3d 15m
Merged PRs (30d)
20

Description

Problem Summary

When deserializing JSON containing a repeated enum field (List<SomeEnum>) with unknown enum values, Wire's GSON integration crashes with IllegalArgumentException: field_name.contains(null) because:

  1. EnumJsonFormatter.fromString() returns null for unknown enum values
  2. ListJsonAdapter adds these null values to the result list
  3. Wire's immutableCopyOf() rejects lists containing null

Affected Code Paths

1. EnumJsonFormatter.kt (wire-runtime)

override fun fromString(value: String): E? {
    return stringToValue[value]
      // If the constant is unknown to our runtime, we return a `Unrecognized` instance if it has
      // been generated.
      ?: unrecognizedClassConstructor?.newInstance(value.toInt())
}

When there's no Unrecognized class (standard enum generation), this returns null for unknown values.

2. GsonJsonIntegration.kt (wire-gson-support)

private class ListJsonAdapter<T>(
    private val single: TypeAdapter<T>,
) : TypeAdapter<List<T?>>() {
    override fun read(reader: JsonReader): List<T?> {
        val result = mutableListOf<T?>()
        reader.beginArray()
        while (reader.hasNext()) {
            result.add(single.read(reader))  // Adds null from EnumJsonFormatter
        }
        reader.endArray()
        return result  // List contains nulls!
    }
}

3. Internal.kt (wire-runtime)

fun <T> immutableCopyOf(name: String, list: List<T>): List<T> {
    if (list.contains(null)) {
        throw IllegalArgumentException("$name.contains(null)")  // CRASH!
    }
    // ...
}

Reproduction

Given this proto:

enum HotelPageBlock {
    UNKNOWN = 0;
    MAP_BLOCK = 1;
    BENEFITS_BLOCK = 2;
}

message RubiricHotelPageBlockOrder {
    repeated HotelPageBlock hotel_page_blocks = 1;
}

And this JSON with an unknown enum value (e.g., server added NEW_BLOCK = 17):

{
    "hotelPageBlocks": ["MAP_BLOCK", "NEW_BLOCK", "BENEFITS_BLOCK"]
}

Deserialization crashes:

java.lang.IllegalArgumentException: hotel_page_blocks.contains(null)
    at com.squareup.wire.internal.Internal__InternalKt.immutableCopyOf(Internal.kt:72)
    at proto.hotels.v1.RubiricHotelPageBlockOrder.<init>(RubiricHotelPageBlockOrder.kt:48)

Expected Behavior

Wire should handle unknown enum values in repeated fields gracefully, either by:

  1. Filtering out unknown values (like proto binary decoding does - stores in unknownFields)
  2. Using the default enum value (first constant, typically UNKNOWN = 0)
  3. Providing a configuration option to choose the behavior

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with EnumJsonFormatter.kt, GsonJsonIntegration.kt, and Internal.kt, then reproduce the repeated-enum JSON failure described in the issue. Resolve which unknown-value behavior is intended—filtering, defaulting, or configuration—and verify that deserialization no longer crashes for the supplied example.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.