This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 3714eba632 @BeanIgnore can be applied to private fields
3714eba632 is described below

commit 3714eba632ec4ce2358679f8d6ec6408e9a13e6b
Author: James Bognar <[email protected]>
AuthorDate: Fri Mar 20 09:31:40 2026 -0400

    @BeanIgnore can be applied to private fields
---
 .../main/java/org/apache/juneau/BeanContext.java   |  1 -
 .../src/main/java/org/apache/juneau/BeanMap.java   |  1 -
 .../src/main/java/org/apache/juneau/BeanMeta.java  | 89 ++++++++++++++++++----
 .../apache/juneau/BeanProxyInvocationHandler.java  |  1 -
 .../juneau/InvalidDataConversionException.java     |  1 -
 .../org/apache/juneau/annotation/BeanIgnore.java   | 21 ++++-
 .../juneau/annotation/BeanIgnoreAnnotation.java    | 25 +++++-
 .../juneau/json5/Json5SerializerSession.java       |  7 ++
 .../org/apache/juneau/objecttools/ObjectRest.java  |  1 -
 .../juneau/serializer/SerializeException.java      |  1 -
 .../apache/juneau/serializer/WriterSerializer.java |  1 -
 .../rest/swagger/BasicSwaggerProviderSession.java  |  8 +-
 .../test/java/org/apache/juneau/BeanMap_Test.java  | 44 +++++++++++
 .../java/org/apache/juneau/ObjectSwap_Test.java    |  1 -
 .../org/apache/juneau/ParserGenerics_Test.java     |  1 -
 .../test/java/org/apache/juneau/ProxyBeanTest.java |  1 -
 .../annotation/BeanIgnoreAnnotation_Test.java      |  2 +-
 .../apache/juneau/annotation/BeanIgnore_Test.java  | 41 ++++++++++
 .../apache/juneau/transforms/ObjectSwapTest.java   |  1 -
 todo/TODO.md                                       |  5 --
 20 files changed, 212 insertions(+), 41 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index 72a548af74..a867940270 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -37,7 +37,6 @@ import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.reflect.Visibility;
 import org.apache.juneau.cp.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.apache.juneau.marshaller.*;
 import org.apache.juneau.serializer.*;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index 04fbaf5e02..a8088f83d8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -30,7 +30,6 @@ import org.apache.juneau.annotation.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.swap.*;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 8329b41717..72fcd484f2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -442,15 +442,8 @@ public class BeanMeta<T> {
                                        bi = 
Introspector.getBeanInfo(c2.inner(), stopClass.inner());
                                else
                                        bi = 
Introspector.getBeanInfo(c2.inner(), null);
-                               if (nn(bi)) {
-                                       for (var pd : 
bi.getPropertyDescriptors()) {
-                                               var builder = 
normalProps.computeIfAbsent(pd.getName(), n -> BeanPropertyMeta.builder(this, 
n));
-                                               if (pd.getReadMethod() != null)
-                                                       
builder.setGetter(info(pd.getReadMethod()));
-                                               if (pd.getWriteMethod() != null)
-                                                       
builder.setSetter(info(pd.getWriteMethod()));
-                                       }
-                               }
+                               if (nn(bi))
+                                       
mergeJavaBeanPropertyDescriptorsIntoNormalProps(bi, normalProps, propertyNamer);
 
                        } else /* Use 'better' introspection */ {
 
@@ -477,12 +470,11 @@ public class BeanMeta<T> {
 
                                        if (x.methodType == GETTER) {
                                                // Two getters.  Pick the best.
-                                               if (nn(bpm.getter)) {
-                                                       if (! 
ap.has(Beanp.class, mi) && ap.has(Beanp.class, bpm.getter)) {
-                                                               m = 
bpm.getter.inner();  // @Beanp annotated method takes precedence.
-                                                       } else if 
(m.getName().startsWith("is") && bpm.getter.getNameSimple().startsWith("get")) {
-                                                               m = 
bpm.getter.inner();  // getX() overrides isX().
-                                                       }
+                                               if (nn(bpm.getter)
+                                                       && ((! 
ap.has(Beanp.class, mi) && ap.has(Beanp.class, bpm.getter))
+                                                               || 
(m.getName().startsWith("is") && 
bpm.getter.getNameSimple().startsWith("get")))) {
+                                                       // @Beanp on existing 
getter takes precedence; else getX() overrides isX().
+                                                       m = bpm.getter.inner();
                                                }
                                                bpm.setGetter(info(m));
                                        }
@@ -1123,6 +1115,7 @@ public class BeanMeta<T> {
                var ci = classMeta;
                var v = beanContext.getBeanMethodVisibility();
                var pn = opt(beanFilter).map(x -> 
x.getPropertyNamer()).orElse(beanContext.getPropertyNamer());
+               var suppressedFromBeanIgnoredFields = 
findSuppressedPropertyNamesFromIgnoredFields(pn);
 
                classHierarchy.get().stream().forEach(c2 -> {
                        for (var m : c2.getDeclaredMethods()) {
@@ -1214,7 +1207,7 @@ public class BeanMeta<T> {
                                if (methodType != UNKNOWN) {
                                        if (nn(bpName) && ! bpName.isEmpty())
                                                n = bpName;
-                                       if (nn(n))
+                                       if (nn(n) && ! 
suppressedFromBeanIgnoredFields.contains(n))
                                                l.add(new BeanMethod(n, 
methodType, m.inner()));
                                }
                        }
@@ -1387,6 +1380,70 @@ public class BeanMeta<T> {
                        .orElse(null);
        }
 
+       /*
+        * Merges standard JavaBeans {@link BeanInfo} property descriptors into 
{@code normalProps}, skipping the class
+        * pseudo-property and logical names suppressed when {@link 
BeanIgnore#ignoreAccessors()} is <jk>true</jk> on a field.
+        */
+       @SuppressWarnings({
+               "java:S135" // Two continues: skip class pseudo-property and 
names suppressed via @BeanIgnore(ignoreAccessors)
+       })
+       private void mergeJavaBeanPropertyDescriptorsIntoNormalProps(BeanInfo 
bi, Map<String,BeanPropertyMeta.Builder> normalProps,
+                       PropertyNamer propertyNamer) {
+               var suppressedFromBeanIgnoredFields = 
findSuppressedPropertyNamesFromIgnoredFields(propertyNamer);
+               for (var pd : bi.getPropertyDescriptors()) {
+                       if (PROP_class.equals(pd.getName()))
+                               continue;
+                       if 
(suppressedFromBeanIgnoredFields.contains(pd.getName()))
+                               continue;
+                       var builder = normalProps.computeIfAbsent(pd.getName(), 
n -> BeanPropertyMeta.builder(this, n));
+                       if (pd.getReadMethod() != null)
+                               builder.setGetter(info(pd.getReadMethod()));
+                       if (pd.getWriteMethod() != null)
+                               builder.setSetter(info(pd.getWriteMethod()));
+               }
+       }
+
+       /*
+        * Property names suppressed from getter/setter discovery because a 
non-static field with that logical name is
+        * annotated with {@link BeanIgnore @BeanIgnore} and {@link 
BeanIgnore#ignoreAccessors()} is <jk>true</jk>.
+        *
+        * <p>
+        * When {@link BeanIgnore#ignoreAccessors()} is <jk>false</jk> (the 
default), ignored fields do not suppress
+        * JavaBean accessors so patterns such as {@code @BeanIgnore} on a 
private field with a public {@code getX()} still
+        * expose {@code x} when field visibility excludes the field.
+        */
+       @SuppressWarnings({
+               "java:S135" // Two continues in inner loop: skip fields without 
@BeanIgnore or without ignoreAccessors
+       })
+       private Set<String> 
findSuppressedPropertyNamesFromIgnoredFields(PropertyNamer propertyNamer) {
+               var s = new HashSet<String>();
+               var ap = beanContext.getAnnotationProvider();
+               for (var c2 : classHierarchy.get()) {
+                       for (var x : c2.getDeclaredFields()) {
+                               if (! x.isNotStatic() || ! 
ap.has(BeanIgnore.class, x))
+                                       continue;
+                               if (! fieldBeanIgnoreIgnoresAccessors(x))
+                                       continue;
+                               var name = ap.find(x).stream()
+                                       .filter(x2 -> x2.isType(Beanp.class) || 
x2.isType(Name.class))
+                                       .map(BeanMeta::name)
+                                       .filter(Objects::nonNull)
+                                       .findFirst()
+                                       
.orElse(propertyNamer.getPropertyName(x.getName()));
+                               if (nn(name))
+                                       s.add(name);
+                       }
+               }
+               return s;
+       }
+
+       private static boolean fieldBeanIgnoreIgnoresAccessors(FieldInfo x) {
+               for (var bi : x.inner().getAnnotationsByType(BeanIgnore.class))
+                       if (bi.ignoreAccessors())
+                               return true;
+               return false;
+       }
+
        /*
         * Finds a bean field by name in the class hierarchy.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
index 1944642dd6..4ec58a6baa 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
@@ -23,7 +23,6 @@ import static org.apache.juneau.commons.utils.Utils.*;
 import java.lang.reflect.*;
 import java.util.*;
 
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 
 /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/InvalidDataConversionException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/InvalidDataConversionException.java
index 2d6847b10d..2723293874 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/InvalidDataConversionException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/InvalidDataConversionException.java
@@ -21,7 +21,6 @@ import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.text.*;
 
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 
 /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnore.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnore.java
index f3890b070c..834d96cded 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnore.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnore.java
@@ -21,6 +21,9 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.*;
+import org.apache.juneau.commons.reflect.Visibility;
+
 /**
  * Ignore classes, fields, and methods from being interpreted as bean or bean 
components.
  *
@@ -30,6 +33,9 @@ import java.lang.annotation.*;
  *     <li>Classes - Forces bean-like classes to be treated as non-beans.
  *     <li>Methods - Forces getters/setters to be ignored.
  *     <li>Fields - Forces bean fields to be ignored.
+ *     <li>
+ *             Fields — Use {@link #ignoreAccessors()} to also exclude 
matching JavaBean accessors from bean metadata (see
+ *             {@link #ignoreAccessors()}).
  *     <li><ja>@Rest</ja>-annotated classes and <ja>@RestOp</ja>-annotated 
methods when an {@link #on()} value is specified.
  * </ul>
  *
@@ -61,11 +67,24 @@ public @interface BeanIgnore {
         */
        String[] description() default {};
 
+       /**
+        * When <jk>true</jk> and this annotation is on a <jk>field</jk>, 
JavaBean accessors (<c>getX</c>/<c>setX</c>,
+        * <c>isX</c>) for the same logical property are also excluded from 
bean metadata.
+        *
+        * <p>
+        * Default is <jk>false</jk>: {@code @BeanIgnore} on a field only 
excludes the field from field-based discovery;
+        * accessors can still expose the property (for example when {@link 
BeanContext.Builder#beanFieldVisibility(Visibility) beanFieldVisibility} is 
{@link Visibility#NONE NONE}). Set to <jk>true</jk> to
+        * omit the property from serialization and parsing while keeping 
accessors for other frameworks.
+        *
+        * @return The annotation value.
+        */
+       boolean ignoreAccessors() default false;
+
        /**
         * Dynamically apply this annotation to the specified 
classes/methods/fields/constructors.
         *
         * <p>
-        * Used in conjunction with {@link 
org.apache.juneau.BeanContext.Builder#applyAnnotations(Class...)} to 
dynamically apply an annotation to an existing class/method/field/constructor.
+        * Used in conjunction with {@link 
BeanContext.Builder#applyAnnotations(Class...)} to dynamically apply an 
annotation to an existing class/method/field/constructor.
         * It is ignored when the annotation is applied directly to 
classes/methods/fields/constructors.
         *
         * <h5 class='section'>Valid patterns:</h5>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnoreAnnotation.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnoreAnnotation.java
index 2891f670ae..c74062f0fe 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnoreAnnotation.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanIgnoreAnnotation.java
@@ -40,7 +40,7 @@ public class BeanIgnoreAnnotation {
        private BeanIgnoreAnnotation() {}
 
        /**
-        * Applies targeted {@link BeanIgnore} annotations to a {@link 
org.apache.juneau.BeanContext.Builder}.
+        * Applies targeted {@link BeanIgnore} annotations to a {@link 
BeanContext.Builder}.
         */
        public static class Applier extends 
AnnotationApplier<BeanIgnore,BeanContext.Builder> {
 
@@ -83,7 +83,7 @@ public class BeanIgnoreAnnotation {
         * Builder class.
         *
         * <h5 class='section'>See Also:</h5><ul>
-        *      <li class='jm'>{@link 
org.apache.juneau.BeanContext.Builder#annotations(Annotation...)}
+        *      <li class='jm'>{@link 
BeanContext.Builder#annotations(Annotation...)}
         * </ul>
         */
        @SuppressWarnings({
@@ -92,6 +92,7 @@ public class BeanIgnoreAnnotation {
        public static class Builder extends AppliedAnnotationObject.BuilderTMFC 
{
 
                private String[] description = {};
+               private boolean ignoreAccessors;
 
                /**
                 * Constructor.
@@ -120,6 +121,17 @@ public class BeanIgnoreAnnotation {
                        return this;
                }
 
+               /**
+                * Sets {@link BeanIgnore#ignoreAccessors()}.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder ignoreAccessors(boolean value) {
+                       ignoreAccessors = value;
+                       return this;
+               }
+
                @Override /* Overridden from AppliedAnnotationObject.Builder */
                public Builder on(String...value) {
                        super.on(value);
@@ -191,16 +203,23 @@ public class BeanIgnoreAnnotation {
        private static class Object extends AppliedOnClassAnnotationObject 
implements BeanIgnore {
 
                private final String[] description;
+               private final boolean ignoreAccessors;
 
                Object(BeanIgnoreAnnotation.Builder b) {
                        super(b);
                        this.description = copyOf(b.description);
+                       this.ignoreAccessors = b.ignoreAccessors;
                }
 
                @Override /* Overridden from BeanIgnore */
                public String[] description() {
                        return description;
                }
+
+               @Override /* Overridden from BeanIgnore */
+               public boolean ignoreAccessors() {
+                       return ignoreAccessors;
+               }
        }
 
        /** Default value */
@@ -214,7 +233,7 @@ public class BeanIgnoreAnnotation {
         * @return A copy of the specified annotation.
         */
        public static BeanIgnore copy(BeanIgnore a, VarResolverSession r) {
-               return 
create().on(r.resolve(a.on())).onClass(a.onClass()).build();
+               return 
create().on(r.resolve(a.on())).onClass(a.onClass()).ignoreAccessors(a.ignoreAccessors()).build();
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5SerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5SerializerSession.java
index 3a2773d9bc..7d4cb9dbca 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5SerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5SerializerSession.java
@@ -34,11 +34,18 @@ import org.apache.juneau.serializer.*;
  *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JsonBasics";>JSON Basics</a>
  * </ul>
  */
+@SuppressWarnings({
+       "resource", // Pipe writer is wrapped by JsonWriter; caller/pipe owns 
lifecycle (same as JsonSerializerSession)
+       "java:S110" // Inheritance depth acceptable for Json5 serializer 
session hierarchy (matches JsonSerializerSession)
+})
 public class Json5SerializerSession extends JsonSerializerSession {
 
        /**
         * Builder class.
         */
+       @SuppressWarnings({
+               "java:S110" // Inheritance depth acceptable for Builder extends 
JsonSerializerSession.Builder hierarchy
+       })
        public static class Builder extends JsonSerializerSession.Builder {
 
                /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
index 091eca992f..6eb021f98d 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
@@ -26,7 +26,6 @@ import java.util.*;
 import org.apache.juneau.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.reflect.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.apache.juneau.parser.*;
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
index 4cc74cf898..42c56ddae5 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
@@ -23,7 +23,6 @@ import java.text.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 
 /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializer.java
index de5f71418a..4e4b014067 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializer.java
@@ -30,7 +30,6 @@ import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.logging.Logger;
 import org.apache.juneau.commons.reflect.*;
-import org.apache.juneau.json.*;
 
 /**
  * Subclass of {@link Serializer} for character-based serializers.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
index 5a93cbef03..cb3da86b20 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
@@ -294,7 +294,7 @@ public class BasicSwaggerProviderSession {
                                .appendIf(nec, SWAGGER_tags,
                                        merge(
                                                omSwagger.getList(SWAGGER_tags),
-                                               toList(r.tags(), 
"@Swagger(tags) on class {0}", c)
+                                               toList(r.tags())
                                        )
                                );
                }
@@ -1197,12 +1197,12 @@ public class BasicSwaggerProviderSession {
                return JsonMap.ofJson(s);
        }
 
-       private JsonList toList(Tag[] aa, String location, 
Object...locationArgs) {
+       private JsonList toList(Tag[] aa) {
                if (aa.length == 0)
                        return null;
                var ol = new JsonList();
                for (var a : aa)
-                       ol.add(toMap(a, location, locationArgs));
+                       ol.add(toMap(a));
                return nullIfEmpty(ol);
        }
 
@@ -1243,7 +1243,7 @@ public class BasicSwaggerProviderSession {
                return nullIfEmpty(om);
        }
 
-       private JsonMap toMap(Tag a, String location, Object...locationArgs) {
+       private JsonMap toMap(Tag a) {
                var om = JsonMap.create();
                Predicate<String> ne = Utils::ne;
                Predicate<Map<?,?>> nem = Utils::ne;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
index 545fd98766..51c57f850a 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
@@ -1792,4 +1792,48 @@ class BeanMap_Test extends TestBase {
                public List<String> getA() { return Collections.emptyList(); }
                public void setA(List<String> v) { a = v; }
        }
+
+       
//====================================================================================================
+       // containsKey with plain beans vs @Beanp(name="*") dyna/extras map
+       
//====================================================================================================
+
+       @Test void z01_containsKey_plainBean_unknownPropertyIsAbsent() {
+               var m = BeanContext.DEFAULT.toBeanMap(new A());
+               assertTrue(m.containsKey("i1"));
+               assertFalse(m.containsKey("noSuchProperty_xyz"));
+       }
+
+       /** Bean with a Map-backed dyna property for extra entries (see 
BasicBeans_Test.B). */
+       public static class DynaExtrasBean {
+               @Beanp(name = "*")
+               public Map<String, Object> extras = new LinkedHashMap<>();
+       }
+
+       @Test void z02_containsKey_dynaBean_unknownKeyNotInExtrasMap() {
+               var bean = new DynaExtrasBean();
+               var m = BeanContext.DEFAULT.toBeanMap(bean);
+               // Dyna map is empty; @Beanp(name="*") does not make arbitrary 
keys appear as present.
+               assertFalse(m.containsKey("notDefinedAnywhere"));
+               assertEquals(set(), m.keySet());
+       }
+
+       @Test void z03_containsKey_dynaBean_trueAfterPutIntoExtras() {
+               var bean = new DynaExtrasBean();
+               var m = BeanContext.DEFAULT.toBeanMap(bean);
+               m.put("dynamicOnly", 123);
+               assertTrue(m.containsKey("dynamicOnly"));
+               assertEquals(123, bean.extras.get("dynamicOnly"));
+       }
+
+       /**
+        * {@link BeanMap#getPropertyMeta(String)} falls back to the dyna ("*") 
property meta for unknown names so that
+        * {@link BeanMap#put(String, Object)} can route writes into the extras 
map; that must not imply the key exists for
+        * {@link Map#containsKey(Object)}.
+        */
+       @Test void 
z04_getPropertyMetaMayFallbackToDyna_containsKeyStillFalseForUnknownKey() {
+               var bean = new DynaExtrasBean();
+               var m = BeanContext.DEFAULT.toBeanMap(bean);
+               assertNotNull(m.getPropertyMeta("phantomKey"));
+               assertFalse(m.containsKey("phantomKey"));
+       }
 }
\ No newline at end of file
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
index a2008764a2..186efdb25c 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
@@ -19,7 +19,6 @@ package org.apache.juneau;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.collections.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.serializer.*;
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ParserGenerics_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/ParserGenerics_Test.java
index 9e6782d601..c899c4abe2 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/ParserGenerics_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ParserGenerics_Test.java
@@ -20,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.*;
 
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.junit.jupiter.api.*;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ProxyBeanTest.java 
b/juneau-utest/src/test/java/org/apache/juneau/ProxyBeanTest.java
index 392cc2b36f..b8287bf682 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ProxyBeanTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ProxyBeanTest.java
@@ -19,7 +19,6 @@ package org.apache.juneau;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.html.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.json5.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.oapi.*;
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnoreAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnoreAnnotation_Test.java
index 63bb6fe1b0..38897e5fe7 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnoreAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnoreAnnotation_Test.java
@@ -49,7 +49,7 @@ class BeanIgnoreAnnotation_Test extends TestBase {
                .build();
 
        @Test void a01_basic() {
-               assertBean(a1, "description,on,onClass", "[a],[b],[X1]");
+               assertBean(a1, "description,ignoreAccessors,on,onClass", 
"[a],false,[b],[X1]");
        }
 
        @Test void a02_testEquivalency() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnore_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnore_Test.java
index 7fc6d151e7..e2260d8aa1 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnore_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/BeanIgnore_Test.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.annotation;
 
 import static org.apache.juneau.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.json5.*;
@@ -120,4 +121,44 @@ class BeanIgnore_Test extends TestBase {
        @Test void a04_beanIgnoreOnBean_usingConfig() {
                assertSerialized(new Bc(), 
Json5Serializer.DEFAULT.copy().applyAnnotations(B1cConfig.class).build(), 
"{f2:2,f3:'xxx',f4:'xxx'}");
        }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @BeanIgnore on private field: suppress accessor pair from bean 
metadata (default + JavaBean introspector)
+       
//------------------------------------------------------------------------------------------------------------------
+
+       public static class PrivateFieldIgnoredWithAccessors {
+               public String visible = "ok";
+
+               @BeanIgnore(ignoreAccessors = true)
+               private String foo = "secret";
+
+               public String getVisible() {
+                       return visible;
+               }
+
+               public void setVisible(String value) {
+                       visible = value;
+               }
+
+               public String getFoo() {
+                       return foo;
+               }
+
+               public void setFoo(String value) {
+                       foo = value;
+               }
+       }
+
+       @Test void a05_beanIgnoreOnPrivateFieldSuppressesGetterProperty() {
+               var bm = 
BeanContext.DEFAULT.getBeanMeta(PrivateFieldIgnoredWithAccessors.class);
+               assertFalse(bm.getProperties().containsKey("foo"), () -> 
"properties: " + bm.getProperties().keySet());
+               assertJson("{visible:'ok'}", new 
PrivateFieldIgnoredWithAccessors());
+       }
+
+       @Test void 
a06_beanIgnoreOnPrivateField_suppressedWithJavaBeanIntrospector() {
+               var bc = BeanContext.create().useJavaBeanIntrospector().build();
+               var s = Json5Serializer.DEFAULT.copy().beanContext(bc).build();
+               
assertFalse(bc.getBeanMeta(PrivateFieldIgnoredWithAccessors.class).getProperties().containsKey("foo"));
+               assertEquals("{visible:'ok'}", s.serialize(new 
PrivateFieldIgnoredWithAccessors()));
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/transforms/ObjectSwapTest.java 
b/juneau-utest/src/test/java/org/apache/juneau/transforms/ObjectSwapTest.java
index 902598b0b8..8829f6bbd8 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/transforms/ObjectSwapTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/transforms/ObjectSwapTest.java
@@ -22,7 +22,6 @@ import org.apache.juneau.*;
 import org.apache.juneau.MediaType;
 import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
-import org.apache.juneau.json5.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.serializer.WriterSerializer.Builder;
 import org.apache.juneau.swap.*;
diff --git a/todo/TODO.md b/todo/TODO.md
index 94557524b9..9bca0bcda1 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -7,9 +7,6 @@
 - Need an easier way to specify this header:
     Content-Disposition: attachment; filename="example.pdf"
 
-- Figure out why this needs a cast:
-       private static final Json5 JSON5_LENIENT = new 
Json5(Json5Serializer.DEFAULT, 
(Json5Parser)Json5Parser.create().ignoreUnknownBeanProperties().build());
-
 - A comprehensive plan for handling large data sets using Suppliers and 
Consumers?
 
 - CSV format supports property names in headers (make sure it can work with 
Suppliers and Consumers above)
@@ -18,9 +15,7 @@
 
 - RestResponse needs a setSerializer() command.
 
-- Verify that you can add @BeanIgnore on a private field with getters/setters.
 - On RestClient when logging with FULL, calling 
RestREsponse.getContent().asString() causes a stream closed exception.
 - Possibility of adding convenience classes for 
okhttp3.mockwebserver.Dispatcher?
 
-- BeanMap.containsKey not working correctly on non-existent properties?
 - Duration.ofDays(7) serialized in hours?
\ No newline at end of file

Reply via email to