Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'@JsonIgnoreProperties' not working with entity relationship in version 2.9.5 and spring-boot 2.0.1 #2011

Closed
robsonfar opened this issue Apr 24, 2018 · 12 comments

Comments

@robsonfar
Copy link

robsonfar commented Apr 24, 2018

Hi. I'm using jackson 2.9.5 (spring-boot 2.0.1) and getting some issue when trying to use @JsonIgnoreProperties in a relationship.

Example:

@Entity
public class Login {
	String email;
	String password;
}

@Entity
public class User {
	String name;
	
	Company company;

	@JsonIgnoreProperties({"password"})
	Login login;
}

I didn't know that @JsonIgnoreProperties could be used that way, but recently reading I saw that it works.
When I try to get one user, the generated JSON should be:

{
	name: "User",
	login: {
		email: "[email protected]"
	}
}

But getting the password (that should be ignored) too:

{
	name: "User",
	login: {
		email: "[email protected]",
		password: "test"
	}
}

When I'm working with Bidirectional relationship it gets worse, because I get error of infinite recursion when trying to get the Company, for example.
Example:

@Entity
public class Company {
	String name;

	@JsonIgnoreProperties({"company"})
	List<User> userList;
}

First time working with spring-boot here, maybe I can have misunderstood but I've read about this kind of use in Issue 70 and Issue 133.

I tried with jackson 2.9.4 (spring-boot 2.0.0) and it didn't work too.

@cowtowncoder
Copy link
Member

cowtowncoder commented Apr 24, 2018

One request: can you provide a simple stand-alone reproduction here, without @Entity annotations or processing? If this can be done I am happy to look into issue -- yes, ignoral should work like that for simple types.

EDIT: Following is NOT true I just found out (see #1060):

---However: ignoral DOES NOT work for elements of lists or arrays, as ignoral at this point only affects value itself, not contents. Support could perhaps be added in future but at this point it does not work and is not something that is easy to make work---

@robsonfar
Copy link
Author

robsonfar commented Apr 24, 2018

I was trying to look further in the problem and it seems to be more related the way Spring Data REST works.
Example:

@RepositoryRestController
public class UserController {
	@Autowired
	private ObjectMapper mapper;

	public ResponseEntity<Object> getUsuarioLogado(HttpServletRequest request) {
		User user = service.getAuthenticatedUser();
		try {
			System.out.println(mapper.writeValueAsString(user));
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		return ResponseEntity.ok(user);
	}
}

With this controller I've 2 different JSON. The JSON printed in console is correct, without the password like it should, but the json I receive in my FrontEnd have this property.
I try to use @JsonIgnoreProperties outside the class, directly in login class, like the following example:

@JsonIgnoreProperties({"password"})
@Entity
public class Login {
	String email;
	String password;
}

@Entity
public class User {
	String name;
	Company company;
	Login login;
}

As I suspected, it works like a charm. Both JSON (printed in console and received in FrontEnd) were without the password field. As a last try, I tried to use just @JsonIgnore directly in Login field, just to be sure that annotations inside the class are working:

@Entity
public class Login {
	String email;
	String password;
}

@Entity
public class User {
	String name;
	Company company;

	@JsonIgnore
	Login login;
}

Ok, both JSON are correct, without the login. So, it seems to me that just @JsonIgnoreProperties are ignored when the serialization occurs by Spring Data REST to use in response.
That way, I really don't know exactly if it's really related with Jackson or with Spring ignoring some Jackson annotations. If you prefer to close this issue it's ok, but if you know something related, please let me know. Thanks!

@robsonfar
Copy link
Author

robsonfar commented Apr 24, 2018

One more thing, @JsonIgnoreProperties worked for me when using with ArrayList, the properties that I have used were ignored, but just work when printing in console, as described in the previous answer. The JSON I get in FrontEnd have the properties that should have been ignored.

@cowtowncoder
Copy link
Member

One quick note: this

ResponseEntity<Object>

could be problematic, as it does not include expected type. I wonder if declaring as ResponseEntity<User> would work better?

@cowtowncoder
Copy link
Member

Also, as per my edit above: you are right, annotation DOES actually work. I love it when I sometimes forget that a feature enhancement has been made to support use case that did not work earlier :)

@robsonfar
Copy link
Author

robsonfar commented Apr 24, 2018

Sorry, I was not trying to return the User entity, but a Resource of User. With User it works ok, but it sends 'id' and other data, but we work with links (HATEOAS), and the Entity alone don't have links.
When trying to aply Resources or Projections, the @JsonIgnoreProperties is ignored.

@RepositoryRestController
public class UserController {
	@Autowired
	private ObjectMapper mapper;

	public ResponseEntity<Object> getUsuarioLogado(HttpServletRequest request) {
		User user = service.getAuthenticatedUser();
		Resource<User> resource = new Resource<User>(user);
		
		try {
			System.out.println(mapper.writeValueAsString(resource));
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		
		return ResponseEntity.ok(resource);
	}
}

Printing in console the resource is identical to the entity, with de adition of links (empty in this case because I didn't add anyone), but in FrontEnd the JSON is ignoring the @JsonIgnoreProperties. As I said, seems some process after the return statement, when Spring hide the 'ids' and add the links.
Change the ResponseEntity of Object to User didn't change anything.

@robsonfar
Copy link
Author

Here a more complete code with the Projections:

@Projection(name = "userProjection", types = { User.class })
public interface UserProjection {
	String getName();

	@JsonIgnoreProperties({"email"})
	LoginProjection getLogin();
}

@Projection(name = "loginProjection", types = { Login.class })
public interface LoginProjection {
	String getEmail();
	String getPassword();
}

@RepositoryRestController
public class UserController {
	@Autowired
	private ProjectionFactory projectionFactory;
	@Autowired
	private ObjectMapper objectMapper;

	public ResponseEntity<Object> getAuthenticatedUser(HttpServletRequest request) {
		User user = objetoService.getAuthenticatedUser();
		UserProjection projection = projectionFactory.createProjection(UserProjection.class, user);
		Resource<UserProjection> resource = new Resource<UserProjection>(projection);
		
		try {
			System.out.println(objectMapper.writeValueAsString(projection));
			System.out.println(objectMapper.writeValueAsString(resource));
		} catch (JsonProcessingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return ResponseEntity.ok(resource);
	}
}

If I use any other annotation inside the Projection it works ok: @JsonIgnore, @jsonmanagedreference, @jsonbackreference, @JsonProperty. All but @JsonIgnoreProperties working.

@GuiRitter
Copy link

GuiRitter commented Apr 24, 2018

I'm also looking for a solution to this problem. I've created a project that reproduces the issue. Clone/download it from here, Debug/Run DemoApplication.java and access localhost:8080/users from any browser.

This is the result. The addresses that read "should not be shown", well, should not be shown.

[...]
    }, {
      "address" : "address",
      "name" : "user",
      "manager" : {
        "address" : "ADDRESS THAT SHOULD NOT BE SHOWN",
        "name" : "manager",
        "manager" : null,
        "anotherManager" : null,
        "_links" : {
          "self" : {
            "href" : "http://localhost:8080/users/1{?projection}",
            "templated" : true
          },
          "manager" : {
            "href" : "http://localhost:8080/users/1/manager{?projection}",
            "templated" : true
          },
          "anotherManager" : {
            "href" : "http://localhost:8080/users/1/anotherManager{?projection}",
            "templated" : true
          }
        }
      },
      "anotherManager" : {
        "address" : "ANOTHER ADDRESS THAT SHOULD NOT BE SHOWN",
        "name" : "eager manager",
        "manager" : null,
        "anotherManager" : null,
        "_links" : {
          "self" : {
            "href" : "http://localhost:8080/users/2{?projection}",
            "templated" : true
          },
          "manager" : {
            "href" : "http://localhost:8080/users/2/manager{?projection}",
            "templated" : true
          },
          "anotherManager" : {
            "href" : "http://localhost:8080/users/2/anotherManager{?projection}",
            "templated" : true
          }
        }
      },
[...]

I created this project using the Spring Boot demo from Spring Initializr and incorporating code from this project, which I found here.

@cowtowncoder
Copy link
Member

Ok first things first: I suspect ResponseEntity<Object> is wrong and has to be replaced. Use of Resource may be fine, but not specifying payload in generic type is just asking for trouble.
Perhaps return value needs to be ResponseEntity<Resource<User>> instead, then. It does not have to be User.
If this is not possible, some of functionality might not be supportable.

Second: I can only help further from Jackson side if there is stand-alone test. This because Spring Boot has its own handling and may use one of many entry points to Jackson, to provide type information (or not).

@GuiRitter
Copy link

Thanks for your response. I wasn't aware that Spring Boot had such responsibility in this matter. I will seek help on their side as well.

@robsonfar
Copy link
Author

I had already try with ResponseEntity<Resource<User>> and it didn't work.
But thanks for your help, I'll try find a way on Spring Boot side too.

@cowtowncoder
Copy link
Member

cowtowncoder commented Apr 25, 2018

@robsonfar Ok good. If it makes no difference it makes no difference. Thank you for testing it.
I just wanted to know if it might be relevant, given that framework will either have to work with declared type or use Type Erased instance type -- and in either case lose some information (in fact the Right Way is to combine the two... but I digress :) ).

I hope Spring Boot folks can share light here. It is possible something is needed from Jackson side, but they are in better position to help troubleshoot first, I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants