Taekwondavide
Taekwondavide

Reputation: 268

How to JSON ignore only when an entity is referenced?

I'm building a REST API using a CRUD repository based on Spring Boot and Spring Data JPA.

I have a problem with JSON serialization. Many entities contain a relation and I want to limit the number of nested entities when retrieving data with get method.

This is an example of two entities that work as expected:

@Entity
@Table
public class Series {

    private @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id;

    @Column(name = "publisher_id")
    private @NotEmpty @NotNull Integer publisherId;

    @Column(name = "title")
    private @NotEmpty @NotNull String title;

    @JsonManagedReference
    @OneToMany(mappedBy = "series")
    private List<Subseries> subseriesList;
    
    public void addSubseries(Subseries subseries) {
        subseriesList.add(subseries);
        subseries.setSeries(this);
    }

}


@Entity
@Table(name = "subseries")
public class Subseries {

    private @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id;

    @Column(name = "series_id", insertable = false, updatable = false)
    private @NotEmpty @NotNull Integer seriesId;

    @Column(name = "title")
    private @NotEmpty @NotNull String title;

    @JsonBackReference
    @ManyToOne
    @JoinColumn(name = "series_id", foreignKey = @ForeignKey(name = "id"))
    private @NotEmpty @NotNull Series series;
}

Both classes implement Serializable interface and have their getters and setters and empty constructor.

This is the output of get method for series/10:

{
    "id": 10,
    "publisherId": 100,
    "title": "Title of the series",
    "subseriesList": [
        {
            "id": 11,
            "seriesId": 10,
            "title": "Title of the first subseries"
        },
        {
            "id": 12,
            "seriesId": 10,
            "title": "Title of the second subseries"
        }
    ]
}

And this is the output for subseries/11:

{
    "id": 11,
    "seriesId": 10,
    "title": "Title of the first subseries"
}

So far, everything is working as expected: I need the subseries for the selected series and that's what I get. The problem arises with this entity, that references Series:

@Entity
@Table(name = "publication")
@NoArgsConstructor
public class Publication {

    private @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id;

    @Column(name = "publisher_id")
    private Integer publisherId;

    @Column(name = "series_id", insertable = false, updatable = false)
    private Integer seriesId;

    @Column(name = "subseries_id")
    private Integer subseriesId;

    @Column(name = "cover_title")
    private @NotEmpty @NotNull String coverTitle;

    @JsonManagedReference
    @ManyToOne
    @JoinColumn(name = "series_id", foreignKey = @ForeignKey(name = "id"))
    private Series series;

    // TODO: @ManyToOne reference for Subseries

}

This entity represents a book, which may belong to a series and possibly to a subseries of that series. I want to get the referenced series and subseries as nested data, but I don't want to get the nested data of the series. This is what I get:

{
    "id": 56,
    "publisherId": 100,
    "seriesId": 10,
    "subseriesId": 11,
    "coverTitle": "Book title",
    "series": {
        "id": 10,
        "publisherId": 100,
        "title": "Title of the series",
        "subseriesList": [
            {
                "id": 11,
                "seriesId": 10,
                "title": "Title of the first subseries"
            },
            {
                "id": 12,
                "seriesId": 10,
                "title": "Title of the second subseries"
            }
        ]
    }
}

I don't need the full list of subseries (also, the second one has nothing to do with the selected publication) and I have no idea of how I could get rid of them for a publication while keeping them for a series. This is what I want:

{
    "id": 56,
    "publisherId": 100,
    "seriesId": 10,
    "subseriesId": 11,
    "coverTitle": "Book title",
    "series": {
        "id": 10,
        "publisherId": 100,
        "title": "Title of the series"
    }
}

This is just an example, but I have the same problem with many other entities in my project.

I had a similar problem here but the situation is different as it works with the main entity and not the nested ones.

I've been suggested to use separate POJOs for the REST service but that would make the code hard to maintain.

What I'm looking for is something like @JsonIgnore or @JsonManagedReference/@JsonBackReference that allows to keep the references when Series is the main entity and discard references when it is a nested entity. Another possible solution would be limiting the number of nested entities to one.

Is there any way to do that?

Upvotes: 3

Views: 1216

Answers (1)

Veselin Davidov
Veselin Davidov

Reputation: 7081

I think you cannot do it with JsonIgnore. It is anotation on the class level and doesn't care if it is referenced or not. Also it is used for backward serialization - so if JSON comes it should populate your class and then you will want that field not to be ignored. So it just goes recursively to the children. You can use @JsonFilter and apply filter on properties runtime. Here is an example:

https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsonfilter.htm

But why would you need that in the first place?

I would say - add another layer of object (POJOs as you said) or we can also call them DTOs ;)

https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/

This might seem like harder for maintainability at first but with time I bet it will pay off. Serializing entities leads to lots of troubles like the one you have. Even worse is when you get circular dependencies and then it becomes unseriazable. Another issue is with data leaking. I know you can hide some properties with @JsonIgnore but that's about it. With the time your project grows you will probably reach to the conclusion that serializing entities doesn't give you the flexibility you want. And last but not least you have better defined classes responsibilities - entities represent the state in the system, DTO is what you send out.

Upvotes: 2

Related Questions