Method hasIgnoreMarker is called not only for fields, but also for the constructor, including the virtual one:
Method called to check whether given property is marked to be ignored. This is used to determine whether to ignore properties, on per-property basis, usually combining annotations from multiple accessors (getters, setters, fields, constructor parameters).
In this case you should ignore only fields, that are not marked properly:
static class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Override
public PropertyName findNameForDeserialization(Annotated a) {
Property property = a.getAnnotation(Property.class);
if (property == null) {
return PropertyName.USE_DEFAULT;
} else {
return PropertyName.construct(property.name());
}
}
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m instanceof AnnotatedField
&& m.getAnnotation(Property.class) == null;
}
}
Example:
class Pojo {
// @Property(name = "id")
Integer id;
// @Property(name = "number")
Integer number;
@Property(name = "assure")
Boolean assure;
@Property(name = "person")
Map<String, String> person;
}
String json =
"{\"id\" : 1, \"number\" : 12345, \"assure\" : true," +
" \"person\" : {\"name\" : \"John\", \"age\" : 23}}";
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
Pojo pojo = mapper.readValue(json, Pojo.class);
System.out.println(pojo);
Pojo{id=null, number=null, assure=true, person={name=John, age=23}}
Note: Custom Property annotation should have RetentionPolicy.RUNTIME (same as JsonProperty annotation):
@Retention(RetentionPolicy.RUNTIME)
public @interface Property {
String name();
}
Answer from user11141611 on Stack OverflowMethod hasIgnoreMarker is called not only for fields, but also for the constructor, including the virtual one:
Method called to check whether given property is marked to be ignored. This is used to determine whether to ignore properties, on per-property basis, usually combining annotations from multiple accessors (getters, setters, fields, constructor parameters).
In this case you should ignore only fields, that are not marked properly:
static class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector {
@Override
public PropertyName findNameForDeserialization(Annotated a) {
Property property = a.getAnnotation(Property.class);
if (property == null) {
return PropertyName.USE_DEFAULT;
} else {
return PropertyName.construct(property.name());
}
}
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m instanceof AnnotatedField
&& m.getAnnotation(Property.class) == null;
}
}
Example:
class Pojo {
// @Property(name = "id")
Integer id;
// @Property(name = "number")
Integer number;
@Property(name = "assure")
Boolean assure;
@Property(name = "person")
Map<String, String> person;
}
String json =
"{\"id\" : 1, \"number\" : 12345, \"assure\" : true," +
" \"person\" : {\"name\" : \"John\", \"age\" : 23}}";
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new CustomAnnotationIntrospector());
Pojo pojo = mapper.readValue(json, Pojo.class);
System.out.println(pojo);
Pojo{id=null, number=null, assure=true, person={name=John, age=23}}
Note: Custom Property annotation should have RetentionPolicy.RUNTIME (same as JsonProperty annotation):
@Retention(RetentionPolicy.RUNTIME)
public @interface Property {
String name();
}
I will suggest a different approach:
In the runtime, with the bytecode instrumentation library Byte Buddy and its Java agent, re-annotate the fields with the proper Jackson Annotations. Simply implement the logic via reflection. See the following example:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.DynamicType.Builder.FieldDefinition.Valuable;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyJsonIgnore {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyJsonProperty {
String name();
}
public class Sample {
public static void main(String[] args) throws JsonProcessingException {
ByteBuddyAgent.install();
ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
ByteBuddy byteBuddy = new ByteBuddy();
AnnotationDescription jsonIgnoreDesc =
AnnotationDescription.Builder.ofType(JsonIgnore.class).build();
Builder<Person> personBuilder = byteBuddy.redefine(Person.class);
for (Field declaredField : Person.class.getDeclaredFields()) {
Valuable<Person> field = personBuilder.field(ElementMatchers.named(declaredField.getName()));
MyJsonProperty myJsonProperty = declaredField.getAnnotation(MyJsonProperty.class);
if (myJsonProperty != null) {
AnnotationDescription jsonPropertyDesc =
AnnotationDescription.Builder.ofType(JsonProperty.class)
.define("value", myJsonProperty.name())
.build();
personBuilder = field.annotateField(jsonPropertyDesc);
}
MyJsonIgnore myJsonIgnore = declaredField.getAnnotation(MyJsonIgnore.class);
if (myJsonIgnore != null) {
personBuilder = field.annotateField(jsonIgnoreDesc);
}
}
personBuilder.make().load(Sample.class.getClassLoader(), classReloadingStrategy);
Person person = new Person("Utku", "Ozdemir", "Berlin");
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(person);
System.out.println(jsonString);
}
}
class Person {
@MyJsonProperty(name = "FIRST")
private String firstName;
@MyJsonProperty(name = "LAST")
private String lastName;
@MyJsonIgnore private String city;
public Person(String firstName, String lastName, String city) {
this.firstName = firstName;
this.lastName = lastName;
this.city = city;
}
}
In the example above, I
- create
MyJsonPropertyandMyJsonIgnoreannotations and aPersonclass for the demonstration purpose - instrument the current Java process with the Byte buddy agent
- create a bytebuddy builder to redefine the
Personclass - loop over the fields of the
Personclass and check for these annotations - add an additional annotation to each of those fields (on the builder), Jackson's
JsonProperty(with the correct field name mapping) andJsonIgnore. - after being done with the fields, make the new class bytecode and load it to the current classloader using the byte buddy agent's class reloading mechanism
- write a person object to the stdout.
It prints, as expected:
{"FIRST":"Utku","LAST":"Ozdemir"}
(the field city is ignored)
This solution might feel like an overkill, but on the other side, it is pretty generic solution - with a few changes in the logic, you could handle all the 3rd party classes (which you are not able to modify) instead of handling them case by case.
You can try to implement custom deserialiser with com.fasterxml.jackson.databind.deser.ContextualDeserializer interface. It allows to create deserialiser instance with a context.
See below example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.Data;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class JsonContextualDeserializerApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = JsonMapper.builder().build();
RelationsInput info = mapper.readValue(jsonFile, RelationsInput.class);
System.out.println(info.toString());
}
}
@Data
class RelationsInput {
@JsonDeserialize(using = RelationStdDeserializer.class)
@RelationTypeInfo(RelationType.OWNER)
private Relation owner;
@JsonDeserialize(using = RelationStdDeserializer.class)
@RelationTypeInfo(RelationType.TENANT)
private Relation tenant;
}
@Data
class Relation {
private int id;
private RelationType relationtype;
}
enum RelationType {OWNER, TENANT}
@Retention(RetentionPolicy.RUNTIME)
@interface RelationTypeInfo {
RelationType value();
}
class RelationStdDeserializer extends StdDeserializer<Relation> implements ContextualDeserializer {
private RelationType propertyRelationType;
public RelationStdDeserializer() {
this(null);
}
public RelationStdDeserializer(RelationType relationType) {
super(Relation.class);
this.propertyRelationType = relationType;
}
@Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deser = ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(Relation.class));
Relation instance = (Relation) deser.deserialize(p, ctxt);
if (this.propertyRelationType != null) {
instance.setRelationtype(this.propertyRelationType);
}
return instance;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
RelationTypeInfo typeInfo = property.getMember().getAllAnnotations().get(RelationTypeInfo.class);
return new RelationStdDeserializer(typeInfo.value());
}
}
Above code for a payload:
{
"owner": {
"id": 1
},
"tenant": {
"id": 2
}
}
prints:
RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))
See also:
- Deserialize to String or Object using Jackson
- How to inject dependency into Jackson Custom deserializer
- Jackson - deserialize inner list of objects to list of one higher level
I am afraid there is not such thing. If you want to use something annotation like you could use custom de-serializers. Create first something like:
@RequiredArgsConstructor
public abstract class RelationTypeDeserializer extends JsonDeserializer<Relation> {
private final RelationType relationType;
@Override
public Relation deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
Relation r = p.readValueAs(Relation.class);
r.setRelationtype(relationType);
return r;
}
}
Then implement the actual ones:
public class OwnerDeserializer extends RelationTypeDeserializer {
public OwnerDeserializer() {
super(RelationType.OWNER);
}
}
and
public class TenantDeserializer extends RelationTypeDeserializer {
public TenantDeserializer() {
super(RelationType.TENANT);
}
}
then use those like:
@Getter @Setter
public class RelationsInput {
@JsonDeserialize(using = OwnerDeserializer.class)
private Relation owner;
@JsonDeserialize(using = TenantDeserializer.class)
private Relation tenant;
}
You can write a custom deserializer:
public static class XyzDeserializer extends JsonDeserializer<Map<Integer, String>> {
@Override
public Map<Integer, String> deserialize(JsonParser p,
DeserializationContext ctxt) throws IOException {
JsonNode rootNode = p.getCodec().readTree(p);
Map<Integer, String> map = new HashMap<>();
rootNode.forEach(n -> map.put(
n.get("fooId").intValue(),
n.get("barVal").asText()
));
return map;
}
}
And use it in this way:
@JsonCreator
public FooBar(
@JsonProperty("blargs")
@JsonDeserialize(using = XyzDeserializer.class) Map<Integer, String> xyz) {
this.xyz = xyz;
}
You need a class for the data
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws IOException {
String json = "{\"blargs\":[\n"
+ " {\"fooId\":888,\"barVal\":\"tacos\"},\n"
+ " {\"fooId\":222,\"barVal\":\"hamburgers\"},\n"
+ " {\"fooId\":444,\"barVal\":\"underpants\"}\n"
+ " ]\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
FooBar fooBar = mapper.readValue(json, FooBar.class);
}
public static class FooBar {
private List<MyObject> xyz;
@JsonCreator
public FooBar(@JsonProperty("blargs") List<MyObject> xyz) {
this.xyz = xyz;
System.out.println(this.xyz);
}
}
public static class MyObject {
private int fooId;
private String barVal;
public int getFooId() {
return fooId;
}
public void setFooId(int fooId) {
this.fooId = fooId;
}
public String getBarVal() {
return barVal;
}
public void setBarVal(String barVal) {
this.barVal = barVal;
}
@Override
public String toString() {
return "MyObject{" + "fooId=" + fooId + ", barVal=" + barVal + '}';
}
}
}