Jackson deserialization using JsonParser is skipping the first key value pair @context

117 views
Skip to first unread message

Aravinda Baliga B

unread,
Sep 3, 2021, 2:44:15 AM9/3/21
to jackson-user
I am building a Jackson deserialization application that can handle the deserialization of `CustomerList` and `Customer`. Users can provide any input and based on the input the code will make the decision whether the provided input JSON is `CustomerList` or `Single Customer`.

Everything is working as expected apart from one small thing. When I provide the `CustomerList` JSON then it would skip the first `key value` pair. In my case, it's skipping the `@Context`.

Following is the `JSON` i am trying to deserialize:
```
{
  "@context": [
    {
      "example": "https://example.com"
    }
  ],
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}
```

Following is my Customer POJO class:
```
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
    private String isA;
    private String name;
    private String age;
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Customer.class, name = "Customer")})
interface BaseResponse {
}
```

Following is the Main:
```
public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);

        //Goto the start of the document
        jsonParser.nextToken();

        try {
            BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
            System.out.println("SINGLE EVENT INPUT" + baseResponse.toString());
        } catch (Exception e) {
            System.out.println("LIST OF CUSTOMER INPUT");
            //Go until the customerList has been reached
            while (!jsonParser.getText().equals("customerList")) {
                System.out.println("Current Token Name : " + jsonParser.getCurrentName());
                if (jsonParser.getCurrentName() != null && jsonParser.getCurrentName().equalsIgnoreCase("@context")) {
                    System.out.println("WITHIN CONTEXT");
                }
                jsonParser.nextToken();
            }
            jsonParser.nextToken();

            //Loop through each object within the customerList and deserilize them
            while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                final JsonNode customerNode = jsonParser.readValueAsTree();
                final String eventType = customerNode.get("isA").asText();
                Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
                System.out.println(event.toString());
            }
        }
    }
}
```

When I run the application I get the following response:
```
LIST OF CUSTOMER INPUT
Current Token Name : isA
Customer(isA=Customer, name=Batman, age=2008)
```

As we can see it's printing only `Current Token Name: isA` I would expect it to print `isA and @Context` because it's present before the `isA`. 

The weird thing that I am seeing is that if I switch the places of `isA` and `@context` in my `JSON` something like this:
```
{
  "isA": "CustomerDocument",
  "@context": [
    {
      "example": "https://example.com"
    }
  ],
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}
```

Then I get the output like this, As you can see now it's reading the `@context` and `isA` both.
```
LIST OF CUSTOMER INPUT
Current Token Name : isA
Current Token Name : @context
WITHIN CONTEXT
Current Token Name : @context
WITHIN CONTEXT
Current Token Name : null
Current Token Name : null
Current Token Name : example
Current Token Name : example
Current Token Name : null
Current Token Name : @context
WITHIN CONTEXT
Customer(isA=Customer, name=Batman, age=2008)
```

Since it's working in the second case I am thinking it's not an issue with my code. But I am not sure what's causing the issue for it to not read in the `firstcase` (with `@context` first and `isA` second).  Can someone please help me understand this issue and provide some workaround as I am trying since yesterday to figure out the problem? Is this a bug in Jackson?

Please note:
The JSON is out of my control as it's coming from another application. Provided JSON is a replica of my actual application JSON.

The second JSON that I can feed to the application is the direct customer rather than a list: (with the current code this is working perfectly)
```
{
  "@context": [
    {
      "example": "https://example.com"
    }
  ],
  "isA": "Customer",
  "name": "Batman",
  "age": "2008"
}

```

Can someone please help me understand why Jackson is skipping the `@context` when provided first and provide some workaround as I am trying since yesterday to figure out the problem? Is this a bug in Jackson?

Tatu Saloranta

unread,
Sep 4, 2021, 6:04:25 PM9/4/21
to jackson-user
Unfortunately this example is quite long so I cannot quite understand the intent; but I have 2 suggestions in trying to trace the behavior.

1. Instead of assuming current token type to be certain thing, verify what the token is: some methods (like "parser.getText()" and "parser.getCurrentName()") are applicable to different token types -- and just because you have "current name" does not necessarily mean you have FIELD_NAME token (in fact it is the value of last name for token following)
2. When a parse exception is thrown, underlying JsonParser is NOT guaranteed to be in a consistent state. Parser should still have information about the last successfully decoded token (but not in case where we are half-way through decoding a new one, and fail), and the location where the problem is encountered. But it SHOULD NOT be assumed that you can further parse content successfully. It is possible no content is available; internal state may be corrupt and so forth.

-+ Tatu +-


--
You received this message because you are subscribed to the Google Groups "jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/8b99e386-325a-45a7-9616-74ec142b45c4n%40googlegroups.com.

Aravinda Baliga B

unread,
Sep 4, 2021, 10:27:52 PM9/4/21
to jackson-user
Hi,

Thanks a lot for your response. Sorry if you were unable to follow my question (stackoverflow). I am sure the problem is happening because point 2 mentioned by you but I am not understanding how to find a workaround for it.

I will try to make it a bit simple so you can understand provide some workaround for me (I have posted this on Stackoverflow as well so the code and text are a bit formatted for easy understanding https://stackoverflow.com/q/69052269/7584240):

I am using Jackson to deserialize a JSON. The input JSON can be of 2 types `CustomerDocument` or `Single Customer`. `CustomerDocument` will have a `CustomerList` which can consist of a huge number of `Customer` and the `Single Customer` will have just a `Single Customer`. Hence, the Jackson has to handle 2 things:

1. Identify if the provided JSON is a `CustomerDocument`, if so then deserialize the elements in `CustomerList` one by one rather than storing the whole thing into `List` so as to reduce the memory usage.
2. Identify if the provided JSON is a `single Customer` and if so then deserialize the `customer` directly.

I am able to achieve this and everything is working as expected but when I provide the `CustomerDocument` then it's unable to read the `@Context` key-value pair as it has been already read during the check for single `Customer` (as mentioned by you in point 2). I guess the below code would make the problems clear:

Following is the JSON I am trying to deserialize:
```
{
  "@context": [
    {
      "example": "https://example.com"
    }
  ],
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}
```

Following is my Customer POJO class:
```
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
    private String isA;
    private String name;
    private String age;
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Customer.class, name = "Customer")})
interface BaseResponse {
}
```

Following is my `Main` class which will read the `JSON InputStream` and make the decision whether the provided input JSON is `CustomerList` or `Single Customer` and then deserialize accordingly:
Following is the output I am getting:

```
LIST OF CUSTOMER INPUT
Current Token Name : isA
Customer(isA=Customer, name=Batman, age=2008)
```

As we can see it's printing only `Current Token Name: isA` I would expect it to print `isA` and `@Context` because it's present before the `isA`. But I am aware that it's not printing because it has already passed that due to the following line of code in `try` block:
```
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
```

Can someone please suggest to me how can I achieve this and is there a better workaround for this issue?

Please note:
1. The `CustomerList` can have a lot of `Customers` hence I do not want to store the whole `CustomerList` into some `List` as it can take a lot of memories. Hence, I am using `JsonParser` so I can read one `JsonToken` at a time.
2. Also, I do not want to create a `CustomerList` class rather than that I want to read one `Customer` at a time and deserialize it.
3. The JSON structure cannot be modified as it's coming from another application and it's a standard format for my application.

Reply all
Reply to author
Forward
0 new messages