[JAVA] How to allow annotations to set members you don't want to output when recursively string serializing an object

You may want to combine the contents of an object into a character string and output it to a log. At that time, I don't want to log this field! Is there something like that? It's a bit annoying to implement toString (), isn't it?

Thing you want to do

Object contents

Cart.java


package jp.co.pmtech.iwata;

public class Cart {
    /**Cart ID*/
    private int id;
    /**Items in the cart*/
    private List<Syohin> syohinList;

    //getter or settert
}

Syohin.java


package jp.co.pmtech.iwata;

public class Syohin {
    /**Product ID*/
    private int id;
    /**Product name*/
    private String name;
    /**price*/
    private BigDecimal price;

    //getter or settert
}

App.java


public final class App {
    public static void main(String[] args) {
        Syohin syohin1 = new Syohin();
        syohin1.setId(1);
        syohin1.setName("Ballpoint pen");
        syohin1.setPrice(new BigDecimal(120));

        Syohin syohin2 = new Syohin();
        syohin2.setId(2);
        syohin2.setName("tissue");
        syohin2.setPrice(new BigDecimal(298));

        List<Syohin> list = new ArrayList<>();
        list.add(syohin1);
        list.add(syohin2);

        Cart cart = new Cart();
        cart.setId(1);
        cart.setSyohinList(list);
    }
}

Try to implement

It can be realized by implementing as follows.

  1. Create an annotation class for fields that are not stringified.
  2. Annotate the target field.
  3. Extend ReflectionToStringBuilder to ignore annotated fields.
  4. Extend ToStringStyle to recursively stringify.

By the way, RecursiveToStringStyle has been increased from commons-lang3.2, and you can use it to recursively convert it to a string. However, since ReflectionToStringBuilder is called internally, the field to be output cannot be controlled. Therefore, I decided to extend ToStringStyle by myself this time. (Details of RecursiveToStringStyle will be described later in the bonus part)

Creating annotation class

LogIgnore.java


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogIgnore {
}

Annotation

This time, I will try not to give the price of the product (Syohin # price).

Syohin.java


public class Syohin {
    /**Product ID*/
    private int id;
    /**Product name*/
    private String name;
    /**price*/
    @LogIgnore
    private BigDecimal price;
}

ReflectionToStringBuilder extension

It seems that ReflectionToStringBuilder # accept () determines the field to be stringified, so override this.

LoggingToStringBuilder.java


public class LoggingToStringBuilder extends ReflectionToStringBuilder {

    public LoggingToStringBuilder(final Object object, final ToStringStyle style) {
        super(object, style);
    }

    public static String reflectionToString(final Object object, final ToStringStyle style) {
        LoggingToStringBuilder builder = new LoggingToStringBuilder(object, style) {
            @Override
            protected boolean accept(final Field field) {
                if (!super.accept(field)) {
                    return false;
                }
                if (field.getAnnotation(LogIgnore.class) != null) {
                    return false;
                }
                return true;
            }
        };

        return builder.toString();
    }
}

Extension of ToStringStyle

Before commons-lang3.2, it seems that you made it yourself.

Create a class that extends ToStringStyle by referring to the following article. (Since the name happens to be commons-lang, I created it with the class name LoggingToStringStyle this time.) Recursive output with reflectionToString-A Memorandum

You can copy and paste the class, but please modify only the following points.

LoggingToStringStyle.java


    protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
        for(String packaz : recursivePackags) {
            if (value.getClass().getCanonicalName().startsWith(packaz)) {
                // buffer.append(ToStringBuilder.reflectionToString(value, this));
                //↓ Change here
                buffer.append(LoggingToStringBuilder.reflectionToString(value, this));
                return;
            }
        }
        buffer.append(value);
    }

Try to move

App.config


    String str = LoggingToStringBuilder.toString(cart, new LoggingToStringStyle("jp.co.pmtech.iwata"));
    System.out.println(str);

result


jp.co.pmtech.iwata.Cart[id=1,syohinList=[jp.co.pmtech.iwata.Syohin[id=1,name=Ballpoint pen],jp.co.pmtech.iwata.Syohin[id=2,name=tissue]]]

The contents of Cart and Syohin are correct, and the price is not displayed.

Bonus: other ways

JSON conversion

The purpose of this time is to output to the log, so I don't care about the output format. Let's display the JSON version using Jackson. If you add the JsonIgnore annotation, it will not be subject to JSON serialization.

Syohin.java


public class Syohin {
    /**Product ID*/
    private int id;
    /**Product name*/
    private String name;
    /**price*/
    @JsonIgnore
    private BigDecimal price;
}

App.java


    ObjectMapper mapper = new ObjectMapper();
    String str = mapper.writeValueAsString(cart);
    System.out.println(str);

result


{"id":1,"syohinList":[{"id":1,"name":"Ballpoint pen"},{"id":2,"name":"tissue"}]}

I can't get the price value.

There is only one problem, it seems that Spring Boot's Rest Controller is converted to JSON with Jackson. I didn't use this method because I wanted to include price in the API return. If you are not using Jackson, you can use this method.

Recursive output with RecursiveToStringStyle of commons-lang

As mentioned above, RecursiveToStringStyle has been added since 3.2 of commons-lang. Let's do it for a moment. Put the following code at the end of main and execute it.

App.java


    String str = ReflectionToStringBuilder.toString(cart, new RecursiveToStringStyle());
    System.out.println(str);

result


jp.co.pmtech.iwata.Cart@610455d6[id=1,syohinList=java.util.ArrayList@2c7b84de{jp.co.pmtech.iwata.Syohin@5a07e868[id=1,name=Ballpoint pen,price=java.math.BigDecimal@36baf30c[intVal=<null>,scale=0]],jp.co.pmtech.iwata.Syohin@76ed5528[id=2,name=tissue,price=java.math.BigDecimal@24d46ca6[intVal=<null>,scale=0]]}] 

It seems that it is recursively converted to a character string, but the object ID is displayed and it is a little noisy. Moreover, the price of Big Decimal has not come out ...

in conclusion

It's a good use, but maybe it's just a log spit out ... For security reasons, you don't want to spit out personal information and passwords in the log. Also, is it a very large object that you want to log? ??

Depending on the requirements, JSON or Commons-lang RecursiveToStringStyle is fine. The source code is posted on github, so please refer to it. https://github.com/pmt-iwata/LoggingToStringBuilder

Recommended Posts

How to allow annotations to set members you don't want to output when recursively string serializing an object
How to write when you want to keep line breaks and output while avoiding XSS in Rails
How to output the value when there is an array in the array
How to output Java string to console screen
When you want to notify an error somewhere when using graphql-spring-boot in Spring Boot