The problem found

The @RestController, @responseBody and other annotations are the most common ones we use when writing Web applications. We often need to return an object to the front end, and SpringMVC helps us serialize it into a JSON object. Today I want to share something that is not too deep, and probably you have encountered at least one or two times, which is the problem of circular references in return objects, and share some of my thoughts.

The problem is very simple and easy to reproduce, directly into the code.

Prepare two objects for circular references:

@Data
public class Person {
    private String name;
    private IdCard idCard;
}

@Data
public class IdCard {
    private String id;
    private Person person;
}
Copy the code

Return the object with a circular reference directly in SpringMVC’s Controller:

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public Person hello(a) {
        Person person = new Person();
        person.setName("kirito");

        IdCard idCard = new IdCard();
        idCard.setId("xxx19950102xxx");

        person.setIdCard(idCard);
        idCard.setPerson(person);

        returnperson; }}Copy the code

< curl localhost:8080/hello > StackOverFlowError

The problem analysis

It’s not hard to understand what’s going on here, but both the stack and common sense should be aware of the fact that SpringMVC uses Jackson by default as an HttpMessageConverter so that when we return an object, Jackson’s Serializer will serialize to a JSON string, as well as the fact that Jackson is unable to parse circular references in Java, which will result in a StackOverFlowError.

Some people say, why do you have circular references? God knows how weird the business scenario is, since Java doesn’t restrict circular references, there must be some reasonable possibility that if you have an interface running smoothly online until one day you run into an object that contains a circular reference, You look at the printed StackOverFlowError stack and start to wonder what small (S) can love (B) to do such a thing!

Assuming that circular references are valid, how do we solve this problem? The simplest solution: one-way maintenance of the association, referring to the idea of one-way mapping in Hibernate’s OneToMany association, involves killing the Person member variable in IdCard. Or, with the help of annotations provided by Jackson, specify fields to ignore circular references, such as this:

@Data
public class IdCard {
    private String id;
    @JsonIgnore
    private Person person;
}
Copy the code

Of course, I also looked at some sources and tried to find a more elegant solution for Jackson, such as these two notes:

@JsonManagedReference
@JsonBackReference
Copy the code

But it didn’t seem to me that they were of much use.

Often, of course, if you don’t abandon the security vulnerabilities fastjson, can also choose to use FastJsonHttpMessageConverter replace the default implementation of Jackson, as follows:

@Bean
public HttpMessageConverters fastJsonHttpMessageConverters(a) {
    //1, define an object to convert the message
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

    //2. Add the fastJSON configuration information
    FastJsonConfig fastJsonConfig = new FastJsonConfig();

    SerializerFeature[] serializerFeatures = new SerializerFeature[]{
        // The output key contains double quotes
        // SerializerFeature.QuoteFieldNames,
        // Whether to output the null field. If it is null, the field will be displayed
        // SerializerFeature.WriteMapNullValue,
        // If the value field is null, the output is 0
        SerializerFeature.WriteNullNumberAsZero,
        // If the List field is null, the output is [], not NULL
        SerializerFeature.WriteNullListAsEmpty,
        // If the character type field is NULL, the output is "" instead of NULL
        SerializerFeature.WriteNullStringAsEmpty,
        // If the Boolean field is null, the output is false, not NULL
        SerializerFeature.WriteNullBooleanAsFalse,
        // Date converter for Date
        SerializerFeature.WriteDateUseDateFormat,
        // Circular reference
        //SerializerFeature.DisableCircularReferenceDetect,
    };

    fastJsonConfig.setSerializerFeatures(serializerFeatures);
    fastJsonConfig.setCharset(Charset.forName("UTF-8"));

    //3. Add configuration information in convert
    fastConverter.setFastJsonConfig(fastJsonConfig);

    //4. Add convert to convertersHttpMessageConverter<? > converter = fastConverter;return new HttpMessageConverters(converter);
}
Copy the code

You can customize some json conversion feature, of course. Today, I mainly focus on SerializerFeature DisableCircularReferenceDetect this attribute, as long as it doesn’t show on this feature, Fastjson handles circular references by default.

With the configuration above, let’s see what it looks like:

{"idCard": {"id":"xxx19950102xxx"."person": {"$ref":".."}},"name":"kirito"}
Copy the code

“$ref”:”..” If you continue to deserialize with FastJSON, you will still be able to parse the same object, which I have described in the previous article “Online Problem Analysis caused by GSON replacing FastJSON”.

Using FastJsonHttpMessageConverter can completely avoid the problem of circular reference, for the return type of scenario is helpful, and @ JsonIgnore can only be applied to the fixed structure of the circular reference object.

thinking

It’s worth mentioning why the standard JSON libraries don’t pay as much attention to circular references. Fastjson makes sure that $ref can be parsed properly during serialization and deserialization, and that the contract information is not defined by the specification of JSON. But in the case of cross-framework, cross-system, cross-language, etc., all this is unknown. At the end of the day, this is a gap between the Java language’s circular references and the fact that the JSON generic specification does not include this concept (maybe the JSON specification describes this feature, but I didn’t find it, please correct me if there is a problem).

What should I choose @ JsonIgnore or use FastJsonHttpMessageConverter? After the above thinking, I think you should be able to choose the right plan according to their own scene.

Summary, if you choose FastJsonHttpMessageConverter, change is bigger, if you have more stock of interface, the proposal ready to return to, to confirm at the same time, to solve the problem of circular reference other incompatible changes, and need to confirm your usage scenario, if the circular reference, Fastjson will use $ref to record reference information, make sure your front end or interface can recognize this information, as this may not be the standard JSON specification. You can also select @jsonIgnore to make minimal changes, but note that if you deserialize again based on the serialization results, the reference information does not automatically recover.