[LANG-1543] [JSON string for maps] ToStringBuilder.reflectionToString

doesnt render nested maps correctly.

Apply a different version of the PR
https://github.com/apache/commons-lang/pull/561

Closes #561.
This commit is contained in:
Gary Gregory 2020-06-28 09:38:52 -04:00
parent 062bc6fe7d
commit 4063df71c3
3 changed files with 117 additions and 0 deletions

View File

@ -81,6 +81,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="add" dev="ggregory">Add ObjectUtils.toString(Object, Supplier&lt;String&gt;).</action>
<action issue="LANG-1567" type="update" dev="ggregory" due-to="Miguel Muñoz, Bruno P. Kinoshita, Gary Gregory">Fixed Javadocs for setTestRecursive() #556.</action>
<action issue="LANG-1542" type="update" dev="ggregory" due-to=" Trần Ngọc Khoa, Gary Gregory">ToStringBuilder.reflectionToString - Wrong JSON format when object has a List of Enum.</action>
<action issue="LANG-1543" type="fix" dev="ggregory" due-to="Swaraj Pal, Gary Gregory">[JSON string for maps] ToStringBuilder.reflectionToString doesnt render nested maps correctly.</action>
<action type="update" dev="ggregory">Make org.apache.commons.lang3.CharSequenceUtils.toCharArray(CharSequence) public.</action>
<action type="add" dev="ggregory">Add org.apache.commons.lang3.StringUtils.substringAfter(String, int).</action>
<action type="add" dev="ggregory">Add org.apache.commons.lang3.StringUtils.substringAfterLast(String, int).</action>

View File

@ -19,7 +19,10 @@
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import org.apache.commons.lang3.ClassUtils;
@ -2595,6 +2598,37 @@ protected void appendDetail(final StringBuffer buffer, final String fieldName, f
appendDetail(buffer, fieldName, valueAsString);
}
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map) {
if (map != null && !map.isEmpty()) {
buffer.append(getContentStart());
boolean firstItem = true;
for (final Entry<?, ?> entry : map.entrySet()) {
final String keyStr = Objects.toString(entry.getKey(), null);
if (keyStr != null) {
if (firstItem) {
firstItem = false;
} else {
appendFieldEnd(buffer, keyStr);
}
appendFieldStart(buffer, keyStr);
final Object value = entry.getValue();
if (value == null) {
appendNullText(buffer, keyStr);
} else {
appendInternal(buffer, keyStr, value, true);
}
}
}
buffer.append(getContentEnd());
return;
}
buffer.append(map);
}
private boolean isJsonArray(final String valueAsString) {
return valueAsString.startsWith(getArrayStart())
&& valueAsString.endsWith(getArrayEnd());

View File

@ -26,6 +26,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -532,6 +534,70 @@ public void testLANG1396() {
assertEquals("{\"Let's \\\"quote\\\" this\":\"value\"}", new ToStringBuilder(base).append("Let's \"quote\" this", "value").toString());
}
@Test
public void testRootMap() {
final Map<String, Object> map = new LinkedHashMap<>();
map.put("k1", "v1");
map.put("k2", 2);
assertEquals("{\"map\":{\"k1\":\"v1\",\"k2\":2}}",
new ToStringBuilder(base).append("map", map).toString());
}
@Test
public void testObjectWithInnerMap() {
final Map<String, Object> map = new LinkedHashMap<>();
map.put("k1", "value1");
map.put("k2", 2);
final InnerMapObject object = new InnerMapObject(){
@Override
public String toString() {
return new ToStringBuilder(this).append("pid", this.pid)
.append("map", this.map).toString();
}
};
object.pid = "dummy-text";
object.map = map;
assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"value1\",\"k2\":2}}}",
new ToStringBuilder(base).append("object", object).toString());
}
@Test
public void testNestedMaps() {
final Map<String, Object> innerMap = new LinkedHashMap<>();
innerMap.put("k2.1", "v2.1");
innerMap.put("k2.2", "v2.2");
final Map<String, Object> baseMap = new LinkedHashMap<>();
baseMap.put("k1", "v1");
baseMap.put("k2", innerMap);
final InnerMapObject object = new InnerMapObject(){
@Override
public String toString() {
return new ToStringBuilder(this).append("pid", this.pid)
.append("map", this.map).toString();
}
};
object.pid = "dummy-text";
object.map = baseMap;
assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"v1\"," +
"\"k2\":{\"k2.1\":\"v2.1\",\"k2.2\":\"v2.2\"}}}}",
new ToStringBuilder(base).append("object", object).toString());
}
@Test
public void testMapSkipNullKey() {
final Map<String, Object> map = new LinkedHashMap<>();
map.put("k1", "v1");
map.put(null, "v2");
assertEquals("{\"map\":{\"k1\":\"v1\"}}",
new ToStringBuilder(base).append("map", map).toString());
}
/**
* An object with nested object structures used to test {@link ToStringStyle.JsonToStringStyle}.
*
@ -616,4 +682,20 @@ public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
/**
* An object with a Map field used to test {@link ToStringStyle.JsonToStringStyle}.
*
*/
static class InnerMapObject {
/**
* Test String field.
*/
String pid;
/**
* Test inner map field.
*/
Map<String, Object> map;
}
}