¿Para qué sirve @JsonIgnoreProperties?

Seguramente, si has tenido que crear una API REST en Spring, te hayas cruzado con esta excepción a la hora de manejar la instancia de una entidad (dependiendo de cómo tengas configurados los métodos HTTP en su respectivo controlador):


Ignoring exception, response committed already: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Document nesting depth (1001) exceeds the maximum allowed (1000, from `StreamWriteConstraints.getMaxNestingDepth()`)
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Document nesting depth (1001) exceeds the maximum allowed (1000, from `StreamWriteConstraints.getMaxNestingDepth()`)
		

Fijémonos en la segunda excepción que aparece:


Could not write JSON: Document nesting depth (1001) exceeds the maximum allowed 
(1000, from `StreamWriteConstraints.getMaxNestingDepth()`)]
		

Si te fijas, este error lo que nos está diciendo es que está excediendo el límite permitido de contenido del JSON. Esto se debe a que se está dando una llamada recursiva entre entidades. Es decir, si tenemos la instancia 1 de la entidad A (A1) y tenemos la instancia 1 de la entidad B (B1), que está relacionada con A1, en la respuesta de la petición veremos algo como que A1 muestra los datos de B1 y B1 muestra los datos de A1 que, a su vez vuelve a mostrar los datos de B1 y así sucesivamente, formando un bucle infinito de llamadas entre ambas instancias que, por suerte, está controlado y se corta solo al llegar a un límite (más o menos funciona como el StackOverflow).


A1 -> B1 -> A1 -> B1 -> A1 -> B1 -> A1 -> B1 -> A1 -> B1 -> A1 -> B1 -> A1 ->...
		

Hay varias formas de solucionar este problema, pero la más sencilla y de la que vamos a hablar en este artículo es mediante la anotación @JsonIgnoreProperties.

@JsonIgnoreProperties forma parte de la librería com.fasterxml.jackson.annotation y se puede utilizar para suprimir la serialización de propiedades o ignorar el procesamiento de las propiedades JSON leídas.

Ahora veamos un ejemplo un poco más complejo que el de las entidades A1 y B1 para explicar más detalladamente el cómo utilizar esta anotación.

Supongamos que tenemos dos entidades: Coche y Motor. Amas clases están relacionadas de la siguiente forma:

  • Un coche ve un motor.
  • Un motor ve un coche.


@Entity
public class Coche {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String modelo;

    @OneToOne
	@JoinColumn(name = "motor_id")
    private Motor motor;
}
		

@Entity
public class Motor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Double volumen;

    @OneToOne(mappedBy = "motor", cascade = CascadeType.ALL)
    private Coche coche;
}
		

Pues bien, en este caso tenemos que decidir si añadir la anotación en Coche o Motor. Esto depende de cada caso, pero vamos a ver ambos y ver cómo se comportaría la aplicación a la hora de devolver la respuesta en formato JSON de una petición GET de HTTP en ambos.

Ignoramos las propiedades de Motor en Coche


@JsonIgnoreProperties(ignoreUnknown = true, value = {"motor"})
		

@Entity
@JsonIgnoreProperties(ignoreUnknown = true, value = {"motor"})
public class Coche {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String modelo;

    @OneToOne
	@JoinColumn(name = "motor_id")
    private Motor motor;
}
		

Si ponemos únicamente @JsonIgnoreProperties en la clase Coche, a la hora de obtener los datos de Coche mediante la petición GET, veremos que ya no nos muestra la información referida al motor. Esto evitará la serielización de propiedades en el caso de que hagamos la petición GET con un coche.


{
	"id": 1,
	"modelo": "A"
}
		

Como se puede observar, la propiedad del motor no aparece, algo que no pasaría si se eliminase la anotación.

Pero, ¿qué pasará a la hora de hacer una petición GET al motor?


{
	"id": 1,
	"volumen": 33.9,
	"coche": {
		"id": 1,
		"modelo": "A"
	}
}
		

En una situación en la que carecieramos de la anotación, al hacer la petición del motor, veríamos que tendríamos el mismo problema con el coche, que empezaría una seralización de propiedades de JSON.

Ignoramos las propiedades de Coche en Motor

Ahora veamos el caso contrario pongamos @JsonIgnoreProperties en la clase Motor y quitémolas de la clase Coche.


@JsonIgnoreProperties(ignoreUnknown = true, value = {"coche"})
		

@Entity
@JsonIgnoreProperties(ignoreUnknown = true, value = {"coche"})
public class Motor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Double volumen;

    @OneToOne(mappedBy = "motor", cascade = CascadeType.ALL)
    private Coche coche;
}
		

En este caso, la respuesta de la petición GET de un coche sería la siguiente:


{
	"id": 1,
	"modelo": "A",
	"motor": {
		"id": 1,
		"volumen": 33.9
	}
}
		

Y, como ya te habrás imaginado, en el caso de hacer la petición GET de un motor, se ignorará la propiedad coche:


{
	"id": 1,
	"volumen": 33.9
}
		

Es evidente que si ponemos la anotación en ambas entidades a la vez, no se mostrarán los datos del motor relacionado con el coche ni los datos relacionados con el motor en ninguna de las peticiones.

Hay que tener en cuenta también que a pesar de que no se muestren los datos de las propiedades indicadas en @JsonIgnoreProperties, la relación entre entidades se sigue realizando exitosamente.

Podemos conseguir el mismo efecto con @JsonIgnore, pero este se coloca justo encima de la propiedad que queremos ignorar, en vez de antes de la clase.