Spring: Nested object does not get deserialized - java

I'm sending a POST-Request from angular to spring. Its getting deserialized mostly correct, but one nested object is getting deserialized as an empty Object.
My Angular interfaces look as follows:
// ClientMedicalModal.ts
export interface ClientMedicalModal {
clientMedicalId: number;
client: ClientModel;
medicalEvidence: MedicalEvidence;
}
// ClientModal.ts
export interface ClientModal {
clientId: number;
}
// MedicalEvidenceModal.ts
export interface MedicalEvidenceModal {
B001: string;
B003: string;
B004: string;
}
My Java-Objects look like this:
public class ClientMedical implements Serializable {
private Integer clientMedicalId;
private Client client;
private MedicalEvidence medicalEvidence;
// getter and setter
}
public class Client implements Serializable {
private Integer clientId;
// getter and setter
}
public class MedicalEvidence implements Serializable {
private String B001;
private String B003;
private String B004;
public String getB001() {
return B001;
}
public MedicalEvidence setB001(String b001) {
B001 = b001;
}
// all other getter and setter
}
When I check the post message from my browser everything seems to be okay:
{"medicalEvidence":{"B001":"Test","B003":"TestMessage","B004":"Whatever"},"client":{"clientId":1}}
Debugging in Spring I get the request, there is a Client-Object with clientId = 1, but the ClientEvidence-Object is empty, all B00* fields are null.
See here the debugging values
Spring form binding binds the form parameters to respective fields for Client class, but MedicalEvidence is blank, so Spring instantiates a new MedicalEvidence class with all fields having null values. Why does the parameters does not get bound to the MedicalEvidence's class fields but to Client's class (and all other classes I'm using the same way)? Btw. It does not work either if I just send MedicalEvidence from Angular. The object params are still all empty.

Try using b001, b002,.. as names, the first letter should not be uppercase in your use case, except you want to use some annotation. And use 'this.' in the setter method.
public class MedicalEvidence implements Serializable {
private String b001;
private String b003;
private String b004;
^^^^
public String getB001() {
return b001;
}
public MedicalEvidence setB001(String b001) {
this.b001 = b001;
^^^^^
}

Related

How to set default values of model class variables from yaml file?

In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}

No primary or default constructor found for interface java.util.List Rest API Spring boot

I am passing a request body to a POST request on postman similar to this:
"name":"Mars",
"artifacts":[
{
"elements":[
{
"name":"carbon",
"amount":0.5,
"measurement":"g"
}
],
"typeName":"typeA"
},
{
"elements":[
{
"name":"hydrogen",
"amount":0.2,
"measurement":"g"
}
],
"typeName":"typeB"
}
]
The create method in the rest controller looks like this.
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars;
Planet and all its nested objects have a default constructor such as:
public Planet() {}
However, I am not able to create a new planet object because of lack of a default constructor. Please help!
EDIT:
Planet class
public class Planet {
#JsonProperty("name")
private String name;
#Field("artifacts")
private List<Artifact> artifacts;
public Planet() {}
public Planet(String name, List<Artifact> artifacts)
{
this.name = name;
this.artifacts = artifacts;
}
//setters and getters
}
Artifact class:
public class Artifact() {
#Field("elements")
private List<Element> elements;
#JsonProperty("typeName")
private String typeName;
public Artifact() {}
public Artifact(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
}
Element class:
public class Element() {
#JsonProperty("elementName")
private String name;
#JsonProperty("amount")
private double amount;
#JsonProperty("measurement")
private String measurement;
public Element() {}
public Element(String name, double amount, String measurement)
{
//assignments
}
}
I had that the same error when I forgot the #RequestBody before the parameter
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
I don't understand what is the issue you are facing, but i can see an error straight away so guessing that is the issue you are facing, i am going to give you a solution.
Create a class which matches your json data structure like this :
Class PlanetData {
private String name;
private List<Planet> artifacts;
public PlanetData(String name, List<Planet> artifacts){
name = name;
artifacts = artifacts;
}
// include rest of getters and setters here.
}
Then your controller should look like this. Basically you needed to put #RequestBody to all the parameters you want to recieve from request JSON. Earlier you only put #RequestBody to name parameter not artifact parameter and since Request Body can be consumed only once, so you need a wrapper class to recieve the complete request body using single #RequestBody annotation.
#RequestMapping("/create")
public String create(#RequestBody PlanetData data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars.toString();
}
Edit : Looking at the Planet class, it also needs some modification
public class Planet {
private String typeName; // key in json should match variable name for proper deserialization or you need to use some jackson annotation to map your json key to your variable name.
private List<Element> elements;
public Planet() {}
public Planet(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
//setters and getters. Remember to change your setters and getter from name to typeName.
}
Hope this solves your issue.
This answer too might help someone.
When you are using spring framework for your API development, you may accidently import a wrong library for RequestBody and RequestHeader annotations.
In my case, I accidently imported library,
io.swagger.v3.oas.annotations.parameters.RequestBody
This could arise the above issue.
Please ensure that, you are using the correct library which is
org.springframework.web.bind.annotation.RequestBody
I guess, it’s trying to call new List() which has no constructor. Try using ArrayList in your signatures.
If it works this way, you have found the error. Then rethink your concept of calling methods, since you would usually want to avoid using implementations of List in method signatures
Make sure your request type is not of type GET
If so it is better not to send data as request body.
you should write as below:
...
public String create(#RequestBody JSONObject requestParams) {
String name=requestParams.getString("name");
List<Planet> planetArtifacts=requestParams.getJSONArray("artifacts").toJavaList(Planet.Class);
...

Conditional property serialization with Jackson and Jersey

I'm unsuccessfully trying to conditionally and dynamically pick which property to serialize to respond to each request with Jersey (using Jackson). The idea behind this is to securely access to properties of objects within a REST API.
I have several objects that I return in API calls that should show/hide fields depending in the user who is authenticated.
For example, lets say I have an object Car
public class Car implements Serializable {
private Long id;
private String VIN;
private String color;
...
}
Lets say that if an user with the ROLE_ADMIN is authenticated, all properties should be returned, but if there isn't a logged user only the first two need to be shown.
I was thinking on building something that's annotation based. Something like:
public class Car implements Serializable {
private Long id;
private String VIN;
#Secured({AccessRole.ROLE_ADMIN})
private String color;
...
}
In this case, the color property should only be returned if the access role of the requesting user matches the ones passed via the annotation.
But I'm unable to get a hook on where should I implement this logic.
What I'm trying to implement is a sort of #JsonIgnore but that's conditional and dynamic. All solutions I found so far are static.
Is this even possible?
Jersey has support for Entity Filtering. Aside from general filtering, it also supports Role-based Entity Filtering using (javax.annotation.security) annotations.
So you can use the #RolesAllowed, #PermitAll, and #DenyAll annotations on the domain model properties
public static class Model {
private String secured;
#RolesAllowed({"ADMIN"})
public String getSecured() { return this.secured; }
}
To make this work though, you need to have set the SecurityContext inside of a request filter. Jersey will look up the SecurityContext to validate the roles. You can read more about it in this post (Note: the entity filtering is separate from any real authorization that is mentioned in that post. But the post does explain about the SecurityContext).
Basically you will have something like (notice the last line where you set the SecurityContext).
#PreMatching
public static class SimpleAuthFilter implements ContainerRequestFilter {
private static final Map<String, User> userStore = new ConcurrentHashMap<>();
static {
userStore.put("peeskillet", new User("peeskillet", Arrays.asList("ADMIN", "USER")));
userStore.put("paulski", new User("paulski", Arrays.asList("USER")));
}
#Override
public void filter(ContainerRequestContext request) throws IOException {
final String authHeader = request.getHeaderString("Authorization");
final String username = authHeader.split("=")[1];
final User user = userStore.get(username);
if (user == null) {
throw new NotAuthorizedException("No good.");
}
request.setSecurityContext(new SimpleSecurityContext(user));
}
}
Where the SimpleSecurityContext is just a class of your own, where you need to override the isUserInRole method and check if the user has the role
private static class SimpleSecurityContext implements SecurityContext {
private final User user;
SimpleSecurityContext(User user) {
this.user = user;
}
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return user.getUsername();
}
};
}
#Override
public boolean isUserInRole(String role) {
return user.getRoles().contains(role);
}
#Override
public boolean isSecure() {
return false;
}
#Override
public String getAuthenticationScheme() {
return "simple";
}
}
That's pretty much it. You will also need to register the SecurityEntityFilteringFeature with the application to make it all work.
See a complete test case in this Gist
You can register a custom MessageBodyWriter https://jersey.java.net/documentation/latest/user-guide.html#d0e6951
The MessageBodyWriter will use your custom logic to decide what to write.
It can be done with #JsonView as #dnault suggested.
http://www.baeldung.com/jackson-json-view-annotation
Your MessageBodyWriter will hold a jackson mapper and you will apply the writerWithView with the matching view class as described in the above link.
EDIT: see this one - Jackson Json serialization: exclude property respect to the role of the logged user

Java validate incoming json matches with the model Class

I need to validate an incoming json from a request body, some fields are required but if the json doesn't have it, they are just retrieved as null:
#Document(collection = "menus")
public class Module {
/** The module name as unique identifier. */
#Indexed(unique = true)
private String name;
/** The internationalization key. */
private String i18n;
/** The Angular state. */
private String state;
...
}
If the incoming json have this structure, without name:
{
"i18n": "INVOICES",
"state": "/invoices"
}
the Module is valid and persisted as:
{
"name": null,
"i18n": "INVOICES",
"state": "/invoices"
}
To validate this incoming json I create a Validator like this:
public class MenuValidator implements Validator {
/** The menu service. */
private MenuService service;
public MenuValidator(MenuService service) {
this.service = service;
}
#Override
public boolean supports(Class<?> objectClass) {
return objectClass.equals(Module.class);
}
#Override
public void validate(Object object, Errors errors) {
try {
String name = ((Module) object).getName());
if(name==null){
errors.rejectValue("name", "name.null","The module name is null");
}
...
}
But this is so tricky, when I have a complex structure and I need to validate all fields. Is there any options to validate my structure without validate field by field?
No i don't think there is any other alternative for such validations. You need to manually check for each property.
Thanks

What is the JSON View class in Jackson and how does it work?

I don't understand what is Jackson's #JsonView(Views.MyClass.class).
I know that I can annotate POJO's fields and methods in this way to filter non-annotated ones from being serialized with JSON. But what is the Views.Myclass class? Is it a template class for Jackson library?
And why can there be many classes inside the Views class? For example like this:
class Views {
static class Public { }
static class ExtendedPublic extends PublicView { }
static class Internal extends ExtendedPublicView { }
}
Why is it needed and how does it work?
Use #JsonView to filter fields depending on the context of serialization. When returning data to a REST client, depending on which REST service was called, we need to limit which data will be serialized while using the same data model.
Lets say we want to create two REST services:
The first service returns some user information like first name and last name but not the messages attached to it.
The second service returns all information from the first service and also the messages attached to the current user.
Sample POJO classes with #JsonView annotation
User Pojo classs
#JsonView(User.Views.Public.class)
public String getFirstname() {
return firstname;
}
#JsonView(User.Views.Public.class)
public String getLastname() {
return lastname;
}
Message Pojo class
#JsonView(User.Views.Internal.class)
public List<Message> getMessages() {
return messages;
}
Rest controller
#RestController
public class SimpleRestController {
#Autowired
SimpleService simpleService;
#RequestMapping(value = "/user/public", method = RequestMethod.GET)
#JsonView(User.Views.Public.class)
public User getUserWithPublicData() {
return simpleService.loadUser();
}
#RequestMapping(value = "/user/internal", method = RequestMethod.GET)
#JsonView(User.Views.Internal.class)
public User getUserWithInternalData() {
return simpleService.loadUser();
}
}

Categories

Resources