You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/01/04 19:57:29 UTC
[groovy] 01/01: GROOVY-5169: JSON output: exclude non-public properties
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY-5169
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit f6ab340292465749edac4dec377fe526aded8d28
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jan 4 13:29:38 2022 -0600
GROOVY-5169: JSON output: exclude non-public properties
---
src/main/java/groovy/lang/MetaClassImpl.java | 47 ++++++-------
.../java/groovy/json/DefaultJsonGenerator.java | 33 ++++++---
.../groovy/json/DefaultJsonGeneratorTest.groovy | 5 ++
.../test/groovy/groovy/json/JsonOutputTest.groovy | 80 +++++++++++++++++-----
4 files changed, 112 insertions(+), 53 deletions(-)
diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 93f78ee..45ca41c 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -2305,51 +2305,46 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
@Override
public List<MetaProperty> getProperties() {
checkInitalised();
- LinkedHashMap<String, MetaProperty> propertyMap = classPropertyIndex.get(theCachedClass);
+ Map<String, MetaProperty> propertyMap = classPropertyIndex.get(theCachedClass);
if (propertyMap == null) {
- // GROOVY-6903: May happen in some special environment, like under Android, due
- // to classloading issues
- propertyMap = new LinkedHashMap<>();
+ // GROOVY-6903: May happen in some special environment, like Android, due to class-loading issues
+ propertyMap = Collections.emptyMap();
}
// simply return the values of the metaproperty map as a List
List<MetaProperty> ret = new ArrayList<>(propertyMap.size());
- for (Map.Entry<String, MetaProperty> stringMetaPropertyEntry : propertyMap.entrySet()) {
- MetaProperty element = stringMetaPropertyEntry.getValue();
- if (element instanceof CachedField) continue;
- // filter out DGM beans
- if (element instanceof MetaBeanProperty) {
- MetaBeanProperty mp = (MetaBeanProperty) element;
- boolean setter = true;
- boolean getter = true;
- MetaMethod getterMetaMethod = mp.getGetter();
+ for (MetaProperty mp : propertyMap.values()) {
+ if (mp instanceof CachedField) continue;
+
+ if (mp instanceof MetaBeanProperty) {
+ MetaBeanProperty mbp = (MetaBeanProperty) mp;
+ // filter out DGM beans
+ boolean getter = true, setter = true;
+ MetaMethod getterMetaMethod = mbp.getGetter();
if (getterMetaMethod == null || getterMetaMethod instanceof GeneratedMetaMethod || getterMetaMethod instanceof NewInstanceMetaMethod) {
getter = false;
}
- MetaMethod setterMetaMethod = mp.getSetter();
+ MetaMethod setterMetaMethod = mbp.getSetter();
if (setterMetaMethod == null || setterMetaMethod instanceof GeneratedMetaMethod || setterMetaMethod instanceof NewInstanceMetaMethod) {
setter = false;
}
if (!setter && !getter) continue;
+
// TODO: I (ait) don't know why these strange tricks needed and comment following as it effects some Grails tests
-// if (!setter && mp.getSetter() != null) {
-// element = new MetaBeanProperty(mp.getName(), mp.getType(), mp.getGetter(), null);
+// if (!setter && mbp.getSetter() != null) {
+// mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), mbp.getGetter(), null);
// }
-// if (!getter && mp.getGetter() != null) {
-// element = new MetaBeanProperty(mp.getName(), mp.getType(), null, mp.getSetter());
+// if (!getter && mbp.getGetter() != null) {
+// mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), null, mbp.getSetter());
// }
- }
-
- if (!permissivePropertyAccess) {
- if (element instanceof MetaBeanProperty) {
- MetaBeanProperty mbp = (MetaBeanProperty) element;
- boolean getterAccessible = canAccessLegally(mbp.getGetter());
- boolean setterAccessible = canAccessLegally(mbp.getSetter());
+ if (!permissivePropertyAccess) {
+ boolean getterAccessible = canAccessLegally(getterMetaMethod);
+ boolean setterAccessible = canAccessLegally(setterMetaMethod);
if (!(getterAccessible && setterAccessible)) continue;
}
}
- ret.add(element);
+ ret.add(mp);
}
return ret;
}
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java b/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
index 5766843..360ff72 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
@@ -21,8 +21,9 @@ package groovy.json;
import org.apache.groovy.json.internal.CharBuf;
import org.apache.groovy.json.internal.Chr;
import groovy.lang.Closure;
+import groovy.lang.MetaBeanProperty;
+import groovy.lang.MetaProperty;
import groovy.util.Expando;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import java.io.File;
import java.math.BigDecimal;
@@ -36,6 +37,7 @@ import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -52,6 +54,7 @@ import static groovy.json.JsonOutput.EMPTY_MAP_CHARS;
import static groovy.json.JsonOutput.EMPTY_STRING_CHARS;
import static groovy.json.JsonOutput.OPEN_BRACE;
import static groovy.json.JsonOutput.OPEN_BRACKET;
+import static java.lang.reflect.Modifier.isPublic;
/**
* A JsonGenerator that can be configured with various {@link JsonGenerator.Options}.
@@ -232,12 +235,27 @@ public class DefaultJsonGenerator implements JsonGenerator {
}
}
- protected Map<?, ?> getObjectProperties(Object object) {
- Map<?, ?> properties = DefaultGroovyMethods.getProperties(object);
- properties.remove("class");
- properties.remove("declaringClass");
- properties.remove("metaClass");
- return properties;
+ protected Map<?, ?> getObjectProperties(final Object object) {
+ List<MetaProperty> metaProperties = org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(object).getProperties();
+
+ Map<String, Object> namesAndValues = new LinkedHashMap<>(metaProperties.size());
+
+ for (MetaProperty mp : metaProperties) {
+ if (!isPublic(mp.getModifiers())) continue; // GROOVY-5169
+
+ // skip write-only property: see File
+ if (mp instanceof MetaBeanProperty) {
+ MetaBeanProperty mbp = (MetaBeanProperty) mp;
+ if (mbp.getField() == null && mbp.getGetter() == null) continue;
+ }
+
+ String name = mp.getName();
+ if (name.equals("class") || name.equals("metaClass") || name.equals("declaringClass")) continue;
+
+ namesAndValues.put(name, mp.getProperty(object));
+ }
+
+ return namesAndValues;
}
/**
@@ -530,5 +548,4 @@ public class DefaultJsonGenerator implements JsonGenerator {
return super.toString() + "<" + this.type.toString() + ">";
}
}
-
}
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
index 48d7c60..a78290f 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
@@ -291,6 +291,11 @@ class DefaultJsonGeneratorTest extends GroovyTestCase {
}
+class JsonObject {
+ String name
+ Map properties
+}
+
class JsonBar {
String favoriteDrink
Date lastVisit
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
index 20c061d..2f053a5 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
@@ -18,19 +18,22 @@
*/
package groovy.json
-import groovy.test.GroovyTestCase
import groovy.transform.Canonical
+import org.junit.Test
import static groovy.json.JsonOutput.toJson
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
-class JsonOutputTest extends GroovyTestCase {
+final class JsonOutputTest {
- // Check for GROOVY-5918
+ @Test // GROOVY-5918
void testExpando() {
assert toJson(new Expando(a: 42)) == '{"a":42}'
assert new JsonBuilder(new Expando(a: 42)).toString() == '{"a":42}'
}
+ @Test
void testBooleanValues() {
assert toJson(Boolean.TRUE) == "true"
assert toJson(Boolean.FALSE) == "false"
@@ -38,6 +41,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson(false) == "false"
}
+ @Test
void testNullValue() {
assert toJson(null) == "null"
// test every overloaded version
@@ -55,6 +59,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson((Map) null) == 'null'
}
+ @Test
void testNumbers() {
assert toJson(-1) == "-1"
assert toJson(1) == "1"
@@ -91,16 +96,19 @@ class JsonOutputTest extends GroovyTestCase {
shouldFail { toJson(Float.NEGATIVE_INFINITY) }
}
+ @Test
void testEmptyListOrArray() {
assert toJson([]) == "[]"
assert toJson([] as Object[]) == "[]"
}
+ @Test
void testListOfPrimitives() {
assert toJson([true, false, null, true, 4, 1.1234]) == "[true,false,null,true,4,1.1234]"
assert toJson([true, [false, null], true, [4, [1.1234]]]) == "[true,[false,null],true,[4,[1.1234]]]"
}
+ @Test
void testPrimitiveArray() {
assert toJson([1, 2, 3, 4] as byte[]) == "[1,2,3,4]"
assert toJson([1, 2, 3, 4] as short[]) == "[1,2,3,4]"
@@ -108,16 +116,19 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson([1, 2, 3, 4] as long[]) == "[1,2,3,4]"
}
+ @Test
void testEmptyMap() {
assert toJson([:]) == "{}"
}
+ @Test
void testMap() {
assert toJson([a: 1]) == '{"a":1}'
assert toJson([a: 1, b: 2]) == '{"a":1,"b":2}'
assert toJson([a: 1, b: true, c: null, d: [], e: 'hello']) == '{"a":1,"b":true,"c":null,"d":[],"e":"hello"}'
}
+ @Test
void testString() {
assert toJson("") == '""'
@@ -153,32 +164,38 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson("\u0019") == '"\\u0019"'
}
+ @Test
void testGString() {
assert toJson("1 + 2 = ${1 + 2}") == '"1 + 2 = 3"'
}
+ @Test
void testStringBuilderBuffer() {
assert toJson(new StringBuilder().append(14).append(' March ').append(2014)) == '"14 March 2014"'
assert toJson(new StringBuffer().append(14).append(' March ').append(2014)) == '"14 March 2014"'
}
+ @Test
void testCharArray() {
char[] charArray = ['a', 'b', 'c']
assert toJson(charArray) == '["a","b","c"]'
}
+ @Test
void testDate() {
def d = Date.parse("yyyy/MM/dd HH:mm:ss Z", "2008/03/04 13:50:00 +0100")
assert toJson(d) == '"2008-03-04T12:50:00+0000"'
}
+ @Test
void testURL() {
assert toJson(new URL("http://glaforge.appspot.com")) == '"http://glaforge.appspot.com"'
assert toJson(new URL('file', '', 'C:\\this\\is\\windows\\path')) == '"file:C:\\\\this\\\\is\\\\windows\\\\path"' // GROOVY-6560
}
+ @Test
void testCalendar() {
def c = GregorianCalendar.getInstance(TimeZone.getTimeZone('GMT+1'))
c.clearTime()
@@ -187,6 +204,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson(c) == '"2008-03-04T12:50:00+0000"'
}
+ @Test
void testComplexObject() {
assert toJson([name: 'Guillaume', age: 33, address: [line1: "1 main street", line2: "", zip: 1234], pets: ['dog', 'cat']]) ==
'{"name":"Guillaume","age":33,"address":{"line1":"1 main street","line2":"","zip":1234},"pets":["dog","cat"]}'
@@ -194,6 +212,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson([[:], [:]]) == '[{},{}]'
}
+ @Test
void testClosure() {
assert toJson({
a 1
@@ -208,11 +227,13 @@ class JsonOutputTest extends GroovyTestCase {
}) == '{"a":1,"b":{"c":2,"d":{"e":[3,{"f":4}]}}}'
}
+ @Test
void testIteratorEnumeration() {
assert toJson([1, 2, 3].iterator()) == '[1,2,3]'
assert toJson(Collections.enumeration([1, 2, 3])) == '[1,2,3]'
}
+ @Test
void testPrettyPrint() {
def json = new JsonBuilder()
@@ -271,6 +292,7 @@ class JsonOutputTest extends GroovyTestCase {
return str.replaceAll(~/\s/, '')
}
+ @Test
void testPrettyPrintStringZeroLen() {
def tree = [myStrings: [str3: 'abc', str0: '']]
def result = stripWhiteSpace(new JsonBuilder(tree).toPrettyString())
@@ -278,6 +300,7 @@ class JsonOutputTest extends GroovyTestCase {
assert result == expected
}
+ @Test
void testPrettyPrintDoubleQuoteEscape() {
def json = new JsonBuilder()
json.text { content 'abc"def' }
@@ -289,6 +312,7 @@ class JsonOutputTest extends GroovyTestCase {
}""".stripIndent()
}
+ @Test
void testSerializePogos() {
def city = new JsonCity("Paris", [
new JsonDistrict(1, [
@@ -335,29 +359,38 @@ class JsonOutputTest extends GroovyTestCase {
}'''.stripIndent()
}
+ @Test
void testMapWithNullKey() {
shouldFail IllegalArgumentException, {
toJson([(null): 1])
}
}
- void testGROOVY5247() {
+ @Test // GROOVY-5247
+ void testTreeMap() {
def m = new TreeMap()
m.a = 1
assert toJson(m) == '{"a":1}'
}
- void testObjectWithDeclaredPropertiesField() {
- def person = new JsonObject(name: "pillow", properties: [state: "fluffy", color: "white"])
- def json = toJson(person)
+ @Test
+ void testDeclaredPropertiesField() {
+ String json = toJson(new JsonObject(name: "pillow", properties: [state: "fluffy", color: "white"]))
assert json == '{"name":"pillow","properties":{"state":"fluffy","color":"white"}}'
}
- void testGROOVY5494() {
- def json = toJson(new JsonFoo(name: "foo"))
+ @Test // GROOVY-5494
+ void testDeclaredPropertiesMethod() {
+ String json = toJson(new Pogo5494(name: "foo"))
assert json == '{"name":"foo","properties":0}'
}
+ static class Pogo5494 {
+ String name
+ int getProperties() { return 0 }
+ }
+
+ @Test
void testCharacter() {
assert toJson('a' as char) == '"a"'
assert toJson('"' as char) == '"\\""'
@@ -371,6 +404,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson('\u0002' as char) == '"\\u0002"'
}
+ @Test
void testEmptyValue() {
assert toJson('') == '""'
assert toJson(['']) == '[""]'
@@ -378,6 +412,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson(new Expando('': '')) == '{"":""}'
}
+ @Test
void testSpecialCharEscape() {
// Map
assert toJson(['"': 0]) == '{"\\"":0}'
@@ -413,6 +448,7 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson({'\u0002' 0}) == '{"\\u0002":0}'
}
+ @Test
void testFile() {
def file = File.createTempFile('test', 'file-json')
file.deleteOnExit()
@@ -423,8 +459,24 @@ class JsonOutputTest extends GroovyTestCase {
assert toJson(dir)
}
+ @Test // GROOVY-5169
+ void testPropertyModifiers1() {
+ assertScript '''
+ class Pogo {
+ public String foo = 'foo' //TODO
+ public String getBar() { 'bar' }
+ protected String getBaz() { 'baz' }
+ private String getBoo() { 'boo' }
+ }
+
+ String json = groovy.json.JsonOutput.toJson(new Pogo())
+ assert json == '{"bar":"bar"}'
+ '''
+ }
}
+//------------------------------------------------------------------------------
+
@Canonical
class JsonCity {
String name
@@ -443,16 +495,6 @@ class JsonStreet {
JsonStreetKind kind
}
-class JsonObject {
- String name
- Map properties
-}
-
-class JsonFoo {
- String name
- int getProperties() { return 0 }
-}
-
enum JsonStreetKind {
street, boulevard, avenue
}