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:28 UTC
[groovy] branch GROOVY-5169 created (now f6ab340)
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a change to branch GROOVY-5169
in repository https://gitbox.apache.org/repos/asf/groovy.git.
at f6ab340 GROOVY-5169: JSON output: exclude non-public properties
This branch includes the following new commits:
new f6ab340 GROOVY-5169: JSON output: exclude non-public properties
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[groovy] 01/01: GROOVY-5169: JSON output: exclude non-public properties
Posted by em...@apache.org.
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
}