Re: [jackson-user] Jackson 2.13.0-rc2 released; more time for testing the Release Candidate, fix bugs

2021-09-04 Thread Tatu Saloranta
On Fri, Sep 3, 2021 at 12:56 PM Nakamura  wrote:
>
> OK, I haven't finished working through the issues yet.  I'm trying to upgrade 
> from 2.11.4 to 2.13.0-rc2.  After fixing some expected breaking changes, here 
> are a few I found:

First of all, thank you for going through testing, reporting issues!

>
> 1. We have tests that compare the values of JsonNodes.  After the upgrade, it 
> started failing when it said that IntNode and ShortNode are not equal.  I 
> haven't pinpointed what has changed (whether it's equality, or how things are 
> turned into ShortNode or IntNode). I'll update the thread once I've figured 
> it out.

Interesting. I do not have a good idea here; although I do know that
the comparison across different node types and esp. for Numeric types
is challenging in general.
This is one reason why external comparator approach with JsonNode method:

public boolean equals(Comparator comparator, JsonNode other) {

is strongly recommended; plain `equals()` is notoriously difficult to
make work reliably (has to work "both ways" across different types
etc).

Having said that, IntNode.equals() and ShortNode.equals() appear
unchanged between 2.11 and 2.13; and both would fail comparison
(they assume strict type equality).
So the issue is probably more with construction of ShortNode vs
IntNode for some input...

>
> 2.  We have a test that tries to read all of the resources files.  When it 
> comes across jackson, it says "File Not Found".
> > /repo1.maven.org/com/fasterxml/jackson/core/jackson-databind/2.13.0-rc2/jackson-databind-2.13.0-rc2.jar!/META-INF/versions/11
> >  (No such file or directory)

That is a weird one for sure.

The location of `module-info.class` did move from main-level (in 2.11
and 2.12) to under `META-INF/versions/11`: this to prevent issues with
some older Java 8
clients. But no idea how this could cause other issues.

> 3.  I'm getting a Malformed class name exception from the 
> jackson-scala-module.
>
>  java.lang.InternalError: Malformed class name
>   at java.lang.Class.getSimpleName(Class.java:1330)
>   at java.lang.Class.getCanonicalName(Class.java:1399)
>   at 
> com.fasterxml.jackson.module.scala.util.TastyUtil$.hasTastyFile(TastyUtil.scala:10)
>   at 
> com.fasterxml.jackson.module.scala.util.ClassW.extendsScalaClass(Classes.scala:12)
>   at 
> com.fasterxml.jackson.module.scala.util.ClassW.extendsScalaClass$(Classes.scala:9)
>   at 
> com.fasterxml.jackson.module.scala.util.ClassW$$anon$1.extendsScalaClass(Classes.scala:34)
>   at 
> com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector$._descriptorFor(ScalaAnnotationIntrospectorModule.scala:157)
>   at 
> com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector$.fieldName(ScalaAnnotationIntrospectorModule.scala:173)
>   at 
> com.fasterxml.jackson.module.scala.introspect.ScalaAnnotationIntrospector$.findImplicitPropertyName(ScalaAnnotationIntrospectorModule.scala:46)
>   at 
> com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.findImplicitPropertyName(AnnotationIntrospectorPair.java:502)
>   at 
> com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector._addFields(POJOPropertiesCollector.java:530)
>   at 
> com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.collectAll(POJOPropertiesCollector.java:421)
>   at 
> com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getPropertyMap(POJOPropertiesCollector.java:386)
>   at 
> com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getProperties(POJOPropertiesCollector.java:233)
>   at 
> com.fasterxml.jackson.databind.introspect.BasicBeanDescription._properties(BasicBeanDescription.java:164)
>   at 
> com.fasterxml.jackson.databind.introspect.BasicBeanDescription.findProperties(BasicBeanDescription.java:239)
>   at 
> com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._findCreatorsFromProperties(BasicDeserializerFactory.java:328)
>   at 
> com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:272)
>   at 
> com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:223)
>   at 
> com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:261)
>   at 
> com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:150)
>   at 
> com.fasterxml.jackson.databind.deser.Deseria

Re: [jackson-user] Jackson deserialization using JsonParser is skipping the first key value pair @context

2021-09-04 Thread Tatu Saloranta
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 +-


On Thu, Sep 2, 2021 at 11:44 PM Aravinda Baliga B  wrote:

> 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": [
> "https://stackoverflow.com";,
> {
>   "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": [
> "https://stackoverflow.com";,
> {
>   "example": "https://example.com";
> }
>   ],
>   "customerList": [
> {
>   "isA": "Customer",
>   "name":

Re: [jackson-user] Jackson deserialization using JsonParser is skipping the first key value pair @context

2021-09-04 Thread Aravinda Baliga B
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": [
"https://stackoverflow.com";,
{
  "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:
```
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());
}
}
}
}
```

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 
stor