Classes having JsonView annotated attributes provide different generated serializers with jackson reflect free optimization in quarkus.
I have the following attribute enabled in our project.
quarkus.rest.jackson.optimization.enable-reflection-free-serializers=true
I have 2 classes one with JsonView enabled on some attributes and the other does not JsonView enabled at all. The byte code generated for both the classes are different. Question is how do I expect the end points to behave. When I run the code, the output is the same.
// Without json view enabled
package org.acme.model;
import java.util.*;
public final class CustomerWithoutJsonView {
int id;
String name;
List<Address> addresses;
public CustomerWithoutJsonView(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
// With Json view enabled
package org.acme.model;
import com.fasterxml.jackson.annotation.*;
import java.util.*;
public final class CustomerWithJsonView {
int id;
String name;
List<Address> addresses;
public CustomerWithJsonView(int id, String name, List<Address> addresses) {
this.id = id;
this.name = name;
this.addresses = addresses;
}
@JsonView({Views.Search.class})
public int getId() {
return id;
}
public String getName() {
return name;
}
@JsonView({Views.Read.class})
public List<Address> getAddresses() {
return addresses;
}
}
package org.acme.model;
public final class Address {
String streetName;
public Address(String streetName) {
this.streetName = streetName;
}
public String getStreetName() {
return streetName;
}
}
// And the following resource class
package org.acme;
import com.fasterxml.jackson.annotation.*;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.acme.model.*;
import java.util.*;
@Path("")
public class GreetingResource {
// All the attributes annotated with search are only included
@GET
@Path("with-json-view-search")
@Produces(MediaType.APPLICATION_JSON)
@JsonView({Views.Search.class})
public CustomerWithJsonView withJsonViewSearch() {
return new CustomerWithJsonView(1, "John Doe",
List.of(new Address("123 Main St"), new Address("456 Elm St")));
}
// All the attributes annotated with either search or view are included
@GET
@Path("with-json-view-read")
@Produces(MediaType.APPLICATION_JSON)
@JsonView({Views.Read.class})
public CustomerWithJsonView withJsonViewRead() {
return new CustomerWithJsonView(1, "John Doe",
List.of(new Address("123 Main St"), new Address("456 Elm St")));
}
// no json view is applicable so all attributes are provided
@GET
@Path("without-json-view")
@Produces(MediaType.APPLICATION_JSON)
public CustomerWithoutJsonView withoutJsonView() {
return new CustomerWithoutJsonView(2, "John Doe");
}
}
// Genereate byte code for the classes
package org.acme.model;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil.SerializationInclude;
import java.io.IOException;
// $VF: synthetic class
public class CustomerWithoutJsonView$quarkusjacksonserializer extends StdSerializer {
public CustomerWithoutJsonView$quarkusjacksonserializer() {
super(CustomerWithoutJsonView.class);
}
public void serialize(Object var1, JsonGenerator var2, SerializerProvider var3) throws IOException {
CustomerWithoutJsonView var4 = (CustomerWithoutJsonView)var1;
SerializationInclude var5 = SerializationInclude.decode(var1, var3);
var2.writeStartObject();
int var6 = var4.getId();
if (var5.shouldSerialize(var6)) {
SerializedString var7 = SerializedStrings$quarkusjacksonserializer.id;
var2.writeFieldName((SerializableString)var7);
var2.writeNumber(var6);
}
String var8 = var4.getName();
if (var5.shouldSerialize(var8)) {
SerializedString var9 = SerializedStrings$quarkusjacksonserializer.name;
var2.writeFieldName((SerializableString)var9);
var2.writeString(var8);
}
var2.writeEndObject();
}
}
package org.acme.model;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.quarkus.resteasy.reactive.jackson.runtime.mappers.JacksonMapperUtil.SerializationInclude;
import java.io.IOException;
// $VF: synthetic class
public class CustomerWithJsonView$quarkusjacksonserializer extends StdSerializer {
public CustomerWithJsonView$quarkusjacksonserializer() {
super(CustomerWithJsonView.class);
}
public void serialize(Object var1, JsonGenerator var2, SerializerProvider var3) throws IOException {
SerializationInclude.decode(var1, var3);
var2.writeStartObject();
var2.writeEndObject();
}
}
As you can see above, the bytecode for CustomerWithJsonView does not include any attribute for serialization. I am just wondering with this difference, do we expect a difference in the behavior of how the data is serialized. As far as I see the response output currently, that is not the case, but looking at the generated serializer I am wondering why is it not behaving differently
When the reflection-free serializers generator meets an annotation that it doesn't know how to manage, like @JsonView
in your case, gives up generating a proper serializer and falls back using the native Jackson's reflection-based algorithm. In essence what is happening there is that the CustomerWithJsonView$quarkusjacksonserializer
is flagged as invalid and not used at all. You see the expected outcome in both cases, but only the one without the annotation is using the reflection-free generated serializer while the other one is going through the normal Jackson's reflection based way.