Skip to content

ProviderBase.readFrom() fails to advance past START_ARRAY for MappingIterator with JSON array input #69

Description

@jamezp

Description

ProviderBase.readFrom() does not handle JSON array input when the entity type is MappingIterator. The method calls p.nextToken() which positions the parser at START_ARRAY, then passes it to reader.readValues(p). However, readValues(JsonParser) creates a MappingIterator with managedParser=false, which does not auto-skip START_ARRAY.

The ObjectReader.readValues(JsonParser) Javadoc explicitly states:

for wrapped sequences, parser MUST NOT point to the surrounding START_ARRAY but rather to the token following it.

This means JSON array input like [{"x":1}, {"x":2}] fails with a MismatchedInputException, while NDJSON input like {"x":1}\n{"x":2} works fine.

Steps to Reproduce

Create a Jakarta REST resource method with a MappingIterator parameter:

@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public List<Point> process(MappingIterator<Point> points) {
  List<Point> result = new ArrayList<>();
  while (points.hasNext()) {
      result.add(points.next());
  }
  return result;
}
POST a JSON array:

Content-Type: application/json

[{"x": 1, "y": 2}, {"x": 3, "y": 4}]

Expected: Successfully iterates over the two Point objects.

Actual: Throws MismatchedInputException: Cannot deserialize value of type Point from Array value (token START_ARRAY).

Root Cause

In ProviderBase.readFrom():

if (p == null || p.nextToken() == null) {  // positions at START_ARRAY for array input
  // ...
}
// ...
if (multiValued) {
  return reader.readValues(p);  // passes parser still at START_ARRAY
}

readValues(JsonParser) calls _newIterator(p, ctxt, deser, false) — the false means managedParser=false, so MappingIterator's constructor does not skip START_ARRAY:

if (managedParser && p.isExpectedStartArrayToken()) {
  p.clearCurrentToken();  // only reached when managedParser=true
}

The existing test (testMappingIterator in SimpleEndpointTestBase) only sends NDJSON, so this path was never exercised.

Suggested Fix

Advance past START_ARRAY before calling readValues(p):

if (multiValued) {
  if (p.currentToken() == JsonToken.START_ARRAY) {
      p.nextToken();
  }
  return reader.readValues(p);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions