4

So I have this Controller class that contain this method:

    @RequestMapping(value = "/x", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<MyRepsonseClass> get(
        @ApiParam(value = "x", required = true) @Valid @RequestBody MyRequestClass request
    ) throws IOException {
        //yada yada my logic here
        return something;
    }

The Json Request gets automatically Mapped to MyRequestClass.java

This is what that class looks like:

@lombok.ToString
@lombok.Getter
@lombok.Setter
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel(description = "description")
public class MyRequestClass {
    private List<SomeClass> attribute1;
    private SomeOtherClass attribute2;
    private YetAnotherClass attribute3;
}

This is an example of a valid json request:

{
    "attribute1": [
        {
            "key":"value"
        }
    ],
    "attribute3": {
        "key":"value"
    }
}

Now, my requirement is to return an Error Message when the request contains an attribute that doesn't exist in the MyRequestClass.java.

As such:

{
    "attribute1": [
        {
            "key":"value"
        }
    ],
    "attribute_that_doesnt_exist": {
        "key":"value"
    }
}

Right now it's not throwing any error. Rather, it's simply not mapping that attribute to anything. Are there annotations I can utilize that can make this happen quickly ?? Thank you.

adbar
  • 438
  • 1
  • 8
  • 18

2 Answers2

3

Create a custom deserializer:

public class MyRequestClassDeserializer extends JsonDeserializer<MyRequestClass> {
    @Override
    public MyRequestClass deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
        MyRequestClass mrc = new MyRequestClass();
        ObjectMapper mapper = new ObjectMapper();
        JsonToken currentToken = null;
        while((currentToken = jsonParser.nextValue()) != null) {
            if(currentToken.equals(JsonToken.END_OBJECT) 
                    || currentToken.equals(JsonToken.END_ARRAY))
                continue;
            String currentName = jsonParser.getCurrentName();
            switch(currentName) {
                case "attribute1":
                    List<SomeClass> attr1 = Arrays.asList(mapper.readValue(jsonParser, SomeClass[].class));
                    mrc.setAttribute1(attr1);
                    break;
                case "attribute2":
                    mrc.setAttribute2(mapper.readValue(jsonParser, SomeOtherClass.class));
                    break;
                case "attribute3":
                    mrc.setAttribute3(mapper.readValue(jsonParser, YetAnotherClass.class));
                    break;
                // <cases for all the other expected attributes>
                default:// it's not an expected attribute
                    throw new JsonParseException(jsonParser, "bad request", jsonParser.getCurrentLocation());
            }
        }
        return mrc;
    }
}  

And add this annotation to your MyRequestClass class: @JsonDeserialize(using=MyRequestClassDeserializer.class)

The only "problem" is that manually deserializing jsons can be a hassle. I would have written the complete code for your case but I'm not good enough at it right now. I might update the answer in the future.

Edit: Done, now it's working code. I thought it was more complicated.

nonzaprej
  • 1,322
  • 2
  • 21
  • 30
  • thank you, i will try this approach in some time and let you know what my results were. – adbar May 24 '17 at 15:49
  • 1
    I edited the code so that it actually works with your example (btw there's an error in your example jsons, in the second attribute the colon is inside the name of the attribute). – nonzaprej May 27 '17 at 15:57
  • This actually works even better than i expected. It automatically validates all the fields inside the attribute1, attribute2, and attribute3 fields in a similar fashion. – adbar May 29 '17 at 14:54
1

Did you try this annotation @JsonIgnoreProperties(ignoreUnknown = false)

If Spring boot, even better, use this property in application.properties(yaml)

spring.jackson.deserialization.fail-on-unknown-properties=true
so-random-dude
  • 15,277
  • 10
  • 68
  • 113
  • I just tried the annotation and for some reason it doesn't work. The property does, but I guess it's global for the whole application and maybe he only needs to return an error on extra json attributes on just one or some endpoints and not all of them. – nonzaprej May 27 '17 at 16:02
  • you are right. It used to work, I dont know whether its a bug or not, now it doesnt work any more. https://stackoverflow.com/questions/41128623/jsonignorepropertiesignoreunknown-false-is-not-working-in-spring-4-2-0-and-up – so-random-dude May 27 '17 at 18:47