Even if I want to convert the contents of a data object to JSON in Java, there is a circular reference ...

What is a "data object" in this article?

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".

environment

Thing you want to do

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"
}

Convert with reflectionToString

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.

Let's create a base class for DTO

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"
}

Beware of circular references!

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.

Also supports circular references

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!

Summary

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

Even if I want to convert the contents of a data object to JSON in Java, there is a circular reference ...
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
[Java] I want to perform distinct with the key in the object
[Rails] I want to send data of different models in a form
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (PowerMockito edition)
I want to var_dump the contents of the intent
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (Javassist second decoction)
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (black magic edition)
I want to be aware of the contents of variables!
[Rails] I want to display the link destination of link_to in a separate tab
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (gray magic that is not so much as black magic)
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (Royal road edition that is neither magic nor anything)
I want to find the MD5 checksum of a file in Java and get the result as a string in hexadecimal notation.
[Java] I want to convert a byte array to a hexadecimal number
I tried to convert a string to a LocalDate type in Java
I want to create a Parquet file even in Ruby
I tried to make a client of RESAS-API in Java
I want to simplify the conditional if-else statement in Java
I want to find out which version of java the jar file I have is available
I want to Flash Attribute in Spring even if I set a reverse proxy! (do not do)
I want to get a list of the contents of a zip file and its uncompressed size
I want to exchange JSON data (object) by Ajax between Java and JavaScript! ~ Spring edition ~
Even if I write the setting of STRICT_QUOTE_ESCAPING in CATALINA_OPTS in tomcat8.5, it is not reflected.
I want to recreate the contents of assets from scratch in the environment built with capistrano
If hash [: a] [: b] [: c] = 0 in Ruby, I want you to extend it recursively even if the key does not exist.
[Swift] When you want to know if the number of characters in a String matches a certain number ...
About the method to convert a character string to an integer / decimal number (cast data) in Java
[Ruby] I want to put an array in a variable. I want to convert to an array
The milliseconds to set in /lib/calendars.properties of Java jre is UTC
I want to make the frame of the text box red when there is an input error
Static function to check if the RGB error of BufferdImage is within the specified ratio in Java
How to check for the contents of a java fixed-length string
I want to change the value of Attribute in Selenium of Ruby
[For beginners] I want to automatically enter pre-registered data in the input form with a selection command.
[Java] Output the result of ffprobe -show_streams in JSON and map it to an object with Jackson
I tried to make a parent class of a value object in Ruby
The story of forgetting to close a file in Java and failing
Memo: [Java] If a file is in the monitored directory, process it.
I want to get the IP address when connecting to Wi-Fi in Java
How to get the absolute path of a directory running in Java
I want to ForEach an array with a Lambda expression in Java
I want to get the field name of the [Java] field. (Old tale tone)
Android development, how to check null in the value of JSON object
[Rails] I want to reset everything because the data in the local environment is strange! What to do before that
If you want to satisfy the test coverage of private methods in JUnit
I received the data of the journey (diary application) in Java and visualized it # 001
I want to play a GIF image on the Andorid app (Java, Kotlin)
Make the JSON of the snake case correspond to the field of the camel case in Java (JVM)
[Java] Is it unnecessary to check "identity" in the implementation of the equals () method?
How to make a unique combination of data in the rails intermediate table
I managed to get a blank when I brought the contents of Beans to the textarea
I want to see the contents of Request without saying four or five
I want to recursively get the superclass and interface of a certain class
I want to output the day of the week
I want to send an email in Java.
Code to escape a JSON string in Java
Feel the passage of time even in Java
[Ruby] "Reference to object" and "Contents of variable"
I wanted to make (a == 1 && a == 2 && a == 3) true in Java
rsync4j --I want to touch rsync in Java.
I want to be eventually even in kotlin