Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/tools/jackson/databind/AnnotationIntrospector.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,16 @@ public boolean isAnnotationBundle(Annotation ann) {
return false;
}

/**
* Method called to merge two annotations of the same type when
* collecting class-level annotations from type hierarchy.
*
* @since 3.1
*/
public Annotation tryMergeClassAnnotation(Annotation existing, Annotation newValue) {
return null;
}

/*
/**********************************************************************
/* Annotations for Object Id handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,13 @@ private AnnotationCollector _addAnnotationsIfNotPresent(AnnotationCollector c,
if (_intr.isAnnotationBundle(ann)) {
c = _addFromBundleIfNotPresent(c, ann);
}
} else {
Class<? extends Annotation> type = ann.annotationType();
Annotation existing = c.asAnnotations().get(type);
Annotation merged = _intr.tryMergeClassAnnotation(existing, ann);
if (merged != null && merged != existing) {
c = c.addOrOverride(merged);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ public boolean isAnnotationBundle(Annotation ann) {
return _primary.isAnnotationBundle(ann) || _secondary.isAnnotationBundle(ann);
}

@Override
public Annotation tryMergeClassAnnotation(Annotation existing, Annotation newValue) {
Annotation merged = _primary.tryMergeClassAnnotation(existing, newValue);
if (merged != null) {
return merged;
}

// [databind#1037]: only support Jackson annotations
// return _secondary.tryMergeClassAnnotation(existing, newValue);
return null;
}

/*
/**********************************************************************
/* General class annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ public boolean isAnnotationBundle(Annotation ann) {
return b.booleanValue();
}

/**
* @since 3.1
*/
@Override
public Annotation tryMergeClassAnnotation(Annotation existing, Annotation newValue) {
// Only handle JsonIgnoreProperties for now
// TODO: It will be added...
if (existing instanceof JsonIgnoreProperties && newValue instanceof JsonIgnoreProperties) {
return _mergeJsonIgnoreProperties((JsonIgnoreProperties) existing, (JsonIgnoreProperties) newValue);
}
return null;
}

/*
/**********************************************************************
/* General annotations
Expand Down Expand Up @@ -1511,4 +1524,49 @@ private DatabindException _databindException(Throwable t, String msg) {
// not optimal as we have no parser/generator/context to pass
return DatabindException.from((JsonParser) null, msg, t);
}

/**
* Helper method to merge two {@link JsonIgnoreProperties} annotations.
* The existing annotation (from more specific class) takes precedence for
* boolean flags, while ignored property names are combined (union).
*
* @since 3.1
*/
private JsonIgnoreProperties _mergeJsonIgnoreProperties(JsonIgnoreProperties existing, JsonIgnoreProperties newValue) {
// Use Value class which already has merge logic
JsonIgnoreProperties.Value existingValue = JsonIgnoreProperties.Value.from(existing);
JsonIgnoreProperties.Value mergedValue = JsonIgnoreProperties.Value.from(newValue).withMerge();

// Merge: existing takes precedence, but names are combined
final JsonIgnoreProperties.Value merged = existingValue.withOverrides(mergedValue);

// Create a synthetic annotation instance
return new JsonIgnoreProperties() {
@Override
public Class<? extends Annotation> annotationType() {
return JsonIgnoreProperties.class;
}

@Override
public String[] value() {
Set<String> ignored = merged.getIgnored();
return ignored.toArray(new String[0]);
}

@Override
public boolean ignoreUnknown() {
return merged.getIgnoreUnknown();
}

@Override
public boolean allowGetters() {
return merged.getAllowGetters();
}

@Override
public boolean allowSetters() {
return merged.getAllowSetters();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tools.jackson.databind.tofix;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

// https://github.com/FasterXML/jackson-databind/issues/1037
public class JsonIgnorePropertiesInheritance1037Test
extends DatabindTestUtil
{
@JsonIgnoreProperties(value = {"generated"}, allowGetters = true)
static class BaseBean1037 {
public String getGenerated() {
return "http://bar.com";
}
}

@JsonIgnoreProperties(value = {"computed"}, allowGetters = true)
static class ReadOnlyBean1037 extends BaseBean1037 {
public int getComputed() {
return 32;
}
}

private final ObjectMapper MAPPER = JsonMapper.builder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();

@Test
public void testReadOnlyProp() throws Exception
{
String json = MAPPER.writeValueAsString(new ReadOnlyBean1037());
assertTrue(json.contains("generated"));
assertTrue(json.contains("computed"));
ReadOnlyBean1037 bean = MAPPER.readValue(json, ReadOnlyBean1037.class);
assertNotNull(bean);
}
}