Skip to content

Parsing list of GenericScimResource fails #311

Description

@manger

Describe the bug
Parsing a SCIM search response as a ListResponse<GenericScimResource> fails with the error
ScimDeserializeException: Cannot convert a non-object JSON to a GenericScimResource
caused by
MismatchedInputException: Trailing token.

To Reproduce
Steps to reproduce the behavior, including a code example, if applicable. Full unit test at the end.

    TypeReference<ListResponse<GenericScimResource>> ref =
        new TypeReference<ListResponse<GenericScimResource>>() {};
    var mapper = JsonUtils.createJsonMapper();
    var users = mapper.readValue(JSON, ref);

Produces the error:

com.unboundid.scim2.common.exceptions.runtime.ScimDeserializeException: Cannot convert a non-object JSON to a GenericScimResource.
 at [No location information] (through reference chain: com.unboundid.scim2.common.messages.ListResponse["Resources"]->java.util.ArrayList[0])
	at com.unboundid.scim2.common.utils.GenericScimObjectDeserializer.deserialize(GenericScimObjectDeserializer.java:69)
	at com.unboundid.scim2.common.utils.GenericScimObjectDeserializer.deserialize(GenericScimObjectDeserializer.java:49)
…
Caused by: tools.jackson.databind.exc.MismatchedInputException: Trailing token (`JsonToken.START_OBJECT`) found after value (bound as `tools.jackson.databind.node.ObjectNode`): not allowed as per `DeserializationFeature.FAIL_ON_TRAILING_TOKENS`
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN]
	at tools.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:67)
	at tools.jackson.databind.DeserializationContext.reportTrailingTokens(DeserializationContext.java:1941)
	at tools.jackson.databind.ObjectReader._verifyNoTrailingTokens(ObjectReader.java:2079)
	at tools.jackson.databind.ObjectReader._bind(ObjectReader.java:1865)
	at tools.jackson.databind.ObjectReader.readValue(ObjectReader.java:1162)
	at com.unboundid.scim2.common.utils.GenericScimObjectDeserializer.deserialize(GenericScimObjectDeserializer.java:64)

Expected behavior
Parsing should succeed, returning a ListResponse holding any number of GenericScimResource objects.

Additional context
Add any other context about the problem here. For example:

  • Java version: 25.0.2
  • SCIM 2 SDK version: 6.0.0

Suspected cause
The bug appears to lie in com.unboundid.scim2.common.utils.GenericScimObjectDeserializer.
It works if deserialising a standalone value, but fails when called to deserialise individual values in an array.
It fails because FAIL_ON_TRAILING_TOKENS is enabled (by default in jackson 3), but that isn't appropriate
when the circumstance is deserialising part of a larger JSON value.

Apparently, the proper approach is to use given DeserializationContext, which has a readTree(jp) method.

The challenge for GenericScimObjectDeserializer might be combining the "SCIM special rules" from JsonUtils.getObjectReader() with the given DeserializationContext.

Maybe adding .without(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) is sufficient?

Unit test

package com.example.scim;

import static org.junit.jupiter.api.Assertions.*;

import com.unboundid.scim2.common.GenericScimResource;
import com.unboundid.scim2.common.messages.ListResponse;
import com.unboundid.scim2.common.types.UserResource;
import com.unboundid.scim2.common.utils.JsonUtils;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;

class GenericScimObjectDeserializerBug {

  static final String JSON =
"""
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
  "totalResults": 2,
  "itemsPerPage": 2,
  "startIndex": 1,
  "Resources": [
    {
      "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
      "id" : "abc-123",
      "userName": "alice@example.com"
    },
    {
      "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
      "id" : "def-456",
      "userName": "bob@example.com"
    }
  ]
}
      """;

  @Test
  void parseListOfGenericScimResource_FAILS() {
    TypeReference<ListResponse<GenericScimResource>> ref =
        new TypeReference<ListResponse<GenericScimResource>>() {};
    var mapper = JsonUtils.createJsonMapper();
    var users = mapper.readValue(JSON, ref);
    assertEquals(2, users.getResources().size());
  }

  @Test
  void parseListOfUserResource() {
    TypeReference<ListResponse<UserResource>> ref =
        new TypeReference<ListResponse<UserResource>>() {};
    var mapper = JsonUtils.createJsonMapper();
    var users = mapper.readValue(JSON, ref);
    assertEquals(2, users.getResources().size());
  }
}

Metadata

Metadata

Assignees

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