Refers to a Java class defined to store data. It is a class that only has "private member variables + getter & setter" or public member variables. I often make it when fetching data from a database with web API or JPA. Sometimes called "DTO".
Even if there is such a data object, there are times when you want to output the data stored here to a log or the like.
public class TestDto
{
public String stringvar;
public Integer integervar;
public int intvar;
public TestDto2 testDto2;
public BigDecimal decimal;
public java.util.Date date;
}
public class TestDto2
{
public String string2;
public Integer integer2;
public int int2;
public TestDto testDto;
}
In such a case, if you can output in JSON like ↓, it will be very easy to see.
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": null
},
"decimal": null,
"date": "2020-08-12T00:00:00.000+0900"
}
Apache Commons ToStringBuilder # reflectionToString
has always been familiar. This is a convenient method that uses reflection to convert the contents of a data object into a string.
Actually, you can specify the output format in the second argument, and JSON is also prepared properly.
ToStringBuilder.reflectionToString(dto, ToStringStyle.JSON_STYLE)
↓ Output result (pretty-printed separately)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": "jp.example.common.dto.TestDto2@ed17bee",
"decimal": null,
"date": "Wed Aug 12 00:00:00 JST 2020"
}
Hmmm, sorry ...
For nested data objects, only the class name and hash value such as " testDto2 ":" jp.example.common.dto.TestDto2@ed17bee ",
are displayed, and the data cannot be displayed.
ToStringBuilder # reflectionToString
usestoString ()
to convert the contents of each member into a string, so if the parent class is not specified like this time, ʻObject # toString` is used and ↑ The output result looks like this.
Then, let's create a base class for a data object like ↓.
import org.apache.commons.lang3.builder.ToStringBuilder;
public class CommonDto implements Serializable
{
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
And make sure that the data object inherits from the ↑ class.
public class TestDto extends CommonDto
{
public String stringvar;
public Integer integervar;
public int intvar;
public TestDto2 testDto2;
public BigDecimal decimal;
public java.util.Date date;
}
public class TestDto2 extends CommonDto
{
public String string2;
public Integer integer2;
public int int2;
public TestDto testDto;
}
In this case, you can output in JSON just by toString ()
.
dto.toString();
↓ Output result (pretty-printed separately)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": null
},
"decimal": null,
"date": "Wed Aug 12 00:00:00 JST 2020"
}
It feels like it's perfect, but there's one pitfall. When a member in a data object references its parent. If you follow the reference obediently, you will be able to refer to it forever.
In fact, if you try to refer to testDto2.testDto
, which is null in the example above, as a parent ...
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
:
It loops infinitely like this. I don't think you usually use this kind of reference, but if you create such an Entity with JPA and relate it to each other, it will return exactly this kind of object. Even if you look at Stack Overflow, you can see some people who are having trouble with this problem. It looks like a pitfall that is unexpectedly easy to encounter.
Since the purpose of this time is log output, I would like to use it without worrying about it, regardless of whether it is a circular reference.
(For example, if the purpose is a REST API response, I don't think that the object obtained from JPA will be responded as it is, so I don't think you need to worry about the circular reference problem.)
As long as I'm using ToStringStyle.JSON_STYLE
, I thought I couldn't handle circular references, so this time I'll try to create an original class that looks a lot like ToStringStyle.JSON_STYLE
.
The entity of ToStringStyle.JSON_STYLE
is JsonToStringStyle
defined as the inner class of ToStringStyle
, so first copy it in its entirety and save it in your project.
↓ Like this (same as JsonToStringStyle
except for the class name)
public class OriginalJsonToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 1L;
private static final String FIELD_NAME_QUOTE = "\"";
/**
* <p>
* Constructor.
* </p>
*
* <p>
* Use the static constant rather than instantiating.
* </p>
*/
OriginalJsonToStringStyle() {
super();
this.setUseClassName(false);
this.setUseIdentityHashCode(false);
:
Then, only the ↓ method rewrites the contents.
Actually, ToStringStyle
originally has a mechanism to handle circular references properly, but the processing by the method of ↓ is a little messy, and that processing is passed through. Because of that, it was in an infinite loop.
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
if (value == null) {
appendNullText(buffer, fieldName);
return;
}
if (value instanceof String || value instanceof Character) {
appendValueAsString(buffer, value.toString());
return;
}
if (value instanceof Number || value instanceof Boolean) {
buffer.append(value);
return;
}
final String valueAsString = value.toString();
if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) {
buffer.append(value);
return;
}
appendDetail(buffer, fieldName, valueAsString);
}
↓ I changed it like this
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
if (value == null) {
appendNullText(buffer, fieldName);
return;
}
if (value instanceof String || value instanceof Character) {
appendValueAsString(buffer, value.toString());
return;
}
if(value instanceof java.util.Date)
{
appendValueAsDate(buffer, (java.util.Date)value);
return;
}
buffer.append(value);
}
//By the way java.util.Output Date in ISO 8601 extended format
protected void appendValueAsDate(StringBuffer buffer, java.util.Date value) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
buffer.append("\"" + df.format(value) + "\"");
}
Now that we have a good sense of circular references, we'll add the following methods: In the case of a circularly referenced object, the normal stringification method as rewritten in ↑ is not used, but it is processed by this method. The output content can be anything, so the result of ʻObjectUtils # identityToString` is the same as the method from which it was overridden. However, double quotes are added before and after so as not to break the JSON format.
@Override
protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) {
buffer.append(FIELD_NAME_QUOTE);
ObjectUtils.identityToString(buffer, value);
buffer.append(FIELD_NAME_QUOTE);
}
↓ Output result (pretty-printed separately)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"int2": 0,
"integer2": 2,
"string2": "CCC",
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"date": "2020-08-12T00:00:00.000+0900",
"decimal": null,
"testDto2": "jp.example.common.dto.TestDto2@5577140b"
}
}
"date": "2020-08-12T00:00:00.000+0900",
"decimal": null
}
Good vibes!
The part that is circularly referenced is output with some duplication, but it has been replaced with the notation " jp.example.common.dto.TestDto2@5577140b "
, and I did not fall into an infinite loop!
Converting a data object to JSON is possible with ToStringBuilder # reflectionToString
.
However, if the object is circularly referenced, such as when using JPA, it was necessary to create a special processing class.
Speaking of JSON conversion, there are GSON and Jackson (ObjectMapper) in addition to ToStringBuilder, but the objects that are circularly referenced are also useless.
Recommended Posts