Disable automatic conversion of non-boolean values to boolean with Jackson - java

I'm looking for a way to disable non-bool values to be used inside a request body. For example:
{
"prop": 23
}
would be converted by jackson into true for myprop inside MyPjo:
public ResponseEntity action(#RequestBody #Valid MyPojo myPojo) {
}
public class MyPojo {
#NotNull
private final boolean myprop;
#JsonCreator
public MyPojo(#JsonProperty(value = "prop", required = true) boolean myprop) {
this.myprop = myprop;
}
}
What would be the best way to disable non-bool values for myprop, and just thrown exception when that happens?

I think the best way in this case for all fields that are validated, you consider this custom validation.
You can create a custom constraint for Boolean values, something like this:
https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html#validator-customconstraints
So you can add your custom constraint on your field
#ValidBoolean
private boolean isReal;

Related

How can I have different names for the same field in different APIs?—Jackson

I have an API whose response is as follows:
{
ruleId:”123”,
ruleName:”Rule1”
}
Now I am introducing a new Api which exactly has these fields but the response should not have name as ruleId ,ruleName but as id,name:
{
id:”123”,
name:”Rule1”
}
I should change in such a way so that the previous Api response should not be impacted.
Thought to use JsonProperty /JsonGetter but it will change the previous Api response as well.
Is there any way that I can have 2 getters for the same field and then use one getter for previous Apis and other one for my purpose? (My concern is only when converting Pojo to JSON)
Can anyone help?
Since you want serialize the object differently in different cases, using jackson mix-in is preferred.
Here is example how to do that.
If your pojo looks something like this:
public class CustomPojo {
private String ruleId;
private String ruleName;
public String getRuleId() {
return ruleId;
}
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
public String getRuleName() {
return ruleName;
}
public void setRuleName(String ruleName) {
this.ruleName = ruleName;
}
}
First, you need to create one interface (or class) like this:
import com.fasterxml.jackson.annotation.JsonProperty;
public interface CostomPojoMixin {
#JsonProperty("Id")
String getRuleId();
#JsonProperty("name")
String getRuleName();
}
This interface will be used to rename fields ruleId and ruleName during serilization.
Then when you have all this setup you can write controller method and customize ObjectMapper:
#GetMapping(value = "/test/mixin")
public String testMixin() throwsJsonProcessingException {
CostomPojo cp = new CostomPojo();
cp.setRuleId("rule");
cp.setRuleName("name");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(CustomPojo.class, CostomPojoMixin.class);
String json = objectMapper.writeValueAsString(cp);
return json;
}
This endpoint should return response like this:
{"Id":"rule","name":"name"}

Make Spring Boot JSON enum deserialization strict, so it does not silently convert invalid values into null

I am using Java Spring Boot #RestController with an object containing enum fields.
Spring automagically deserializes the JSON to the MyRequest object.
#RestController
public class MyController {
#PostMapping(path = "/operation")
public ResponseEntity<MyResponse> operation(#Valid #RequestBody MyRequest request) {
...
}
}
public class MyRequest {
private MyEnum1 field1;
private MyEnum2 field2;
private MyEnum3 field3;
private MyEnum4 field4;
private MyEnum5 field5;
private MyEnum6 field6;
... // really a lot of various enum fields!
}
public enum MyEnum1 {
VAL1, VAL2, VAL3;
}
The problem is that if the JSON contains completely invalid value of the enum field, the deserializer silently converts them to null, without any exception.
{
"field1": "BLAHBLAH",
...
}
This is user-unfriendly and treacherous.
I know that I may write custom JSON deserializers for each enum, but the solution is cumbersome and non-elegant.
Is there a way to globally set the JSON enum deserializer to a "strict mode", so if the value is invalid it throws an exception? If so, how and where?
That feature should be disabled by default.
But if you want to set it explicitly you can do it like this:
in your properties:
spring.jackson.deserialization.read-unknown-enum-values-as-null=false
or as an alternative in a configuration class (actually any bean would work, just make sure it happens early):
#Autowired
public void configureJackson(ObjectMapper objectMapper) {
objectMapper.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}
Because it should actually be like this by default, I am wondering why it is not for you. Do you enable it somewhere? Which Spring Boot version are you using?

Use #JsonSerialize(using=MySerializer.class) on a #OneToMany Set<Object>

In a Spring Boot/Spring Data Rest project i have issues to use a custom JsonSerializer<Set<Object>> on a #OneToMany property. When i do an HTTP GET /collection request i have the following error:
Failed to write HTTP message:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: Can not override serializer (through
reference chain:
org.springframework.hateoas.Resources["_embedded"]->java.util.UnmodifiableMap["analogParameters"]->java.util.ArrayList[0]);
nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not override
serializer (through reference chain:
org.springframework.hateoas.Resources["_embedded"]->java.util.UnmodifiableMap["analogParameters"]->java.util.ArrayList[0])
Below is an extract of my entity class:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="output_parameter_id")
#JsonSerialize(using=InputParametersSerializer.class)
//#Transcient
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();
public Set<InputParameter> getInputParameters() {
return inputParameters;
}
public void setInputParameters(Set<InputParameter> inputParameters) {
this.inputParameters = inputParameters;
}
And the JsonSerializer<Set<InputParameter>>
public class InputParametersSerializer
extends JsonSerializer<Set<InputParameter>> {
static final long serialVersionUID = 123L;
public void serialize (Set<InputParameter> ips, JsonGenerator jg,
SerializerProvider sp)
throws IOException {
jg.writeString("Yeah");
}
}
If i remove #OneToMany and define the property as #transient it works as expected.
InputParameter entity has no Repository associated (it is not exported as a rest resource).
How can a make use of a JsonSerializer on a #OneToMany property?
I ran into a very similar issue while using Spring Boot 2.1.0. Adding a custom serializer, both with using and keyUsing, works fine, but a custom deserializer with a #OneToMany annotated field throws out the same JsonMappingException: Can not override serializer message you got, while with an #ElementCollection it just plain gets ignored. I suspect Spring Data Rest does some undocumented magic in order to take care of the (de)serialization of these kinds of fields that does not play nice with the addition of a custom deserializer. My workaround to this was adding an extra JSON field through an annotated getter and setter. With your example, it would look like:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="output_parameter_id")
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();
public Set<InputParameter> getInputParameters() {
return inputParameters;
}
public void setInputParameters(Set<InputParameter> inputParameters) {
this.inputParameters = inputParameters;
}
#JsonSerialize(using=InputParametersSerializer.class)
public Set<InputParameter> getInputParametersSet() {
return getInputParameters();
}
#JsonDeserialize(using=InputParametersDeserializer.class)
public void setInputParametersSet(Set<InputParameter> inputParameters) {
setInputParameters(inputParameters);
}
Which will output something like
{
...
"inputParameters" : ...,
"inputParametersSet" : ...,
...
}
While not ideal, serialization and deserialization of this field works as expected.
alternatively, in order to keep the field name, a similar workaround worked with #ElementCollection but not with #OneToMany:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="output_parameter_id")
#JsonIgnore
private Set<InputParameter> inputParameters = new HashSet<InputParameter>();
public Set<InputParameter> getInputParameters() {
return inputParameters;
}
public void setInputParameters(Set<InputParameter> inputParameters) {
this.inputParameters = inputParameters;
}
#JsonProperty("inputParameters")
#JsonSerialize(using=InputParametersSerializer.class)
public Set<InputParameter> getInputParametersSet() {
return getInputParameters();
}
#JsonProperty("inputParameters")
#JsonDeserialize(using=InputParametersDeserializer.class)
public void setInputParametersSet(Set<InputParameter> inputParameters) {
setInputParameters(inputParameters);
}
In the end I had to go with the first approach.

JSR 303 Bean validation

I have two fields in my bean
String key,
String value,
When field key="A" , "value" should follow a particular Regex
for other "key" - it can be anything.
How would I define this validation on value based on key.
You can use class-level constraints.
1- Annotate your bean with class-level custom constraint annotation:
#ValidKeyValue
public class MyBean {
private String key;
private String value;
...
}
2- Create the custom annotation and its validator.
3- Implement your validation logic in the isValid method:
#Override
public boolean isValid(MyBean myBean, ConstraintValidatorContext context) {
if ("A".equals(myBean.getKey())) {
// case 1
} else {
// case 2
}
}

How to implement Jackson custom serialization outside a domain bean?

I have a Spring managed bean...
#Component("Foobean")
#Scope("prototype")
public class foobean {
private String bar1;
private String bar2;
public String getBar1() {
return bar1;
}
public void setBar1(String bar1) {
this.bar1 = bar1;
}
public String getBar2() {
return bar2;
}
public void setBar2(String bar2) {
this.bar2 = bar2;
}
}
...and because I am using Dojo Dgrid to display an ArrayList of this bean, I am returning it into the controller as a JSON string:
#Controller
#RequestMapping("/bo")
public class FooController {
#Autowired
private FooService fooService
#RequestMapping("action=getListOfFoos*")
#ResponseBody
public String clickDisplayFoos(
Map<String, Object> model) {
List<Foobean> foobeans = fooService.getFoobeans();
ObjectMapper objMapper = new ObjectMapper();
String FooJson = null;
try {
FooJson = objMapper.writeValueAsString(foobeans);
} catch (JsonGenerationException e) {
etc.
}
However, my grid needs an additional column which will contain a valid action for each Foo; that action is not really dependent on any data in individual Foos -- they'll all have the same valid action -- repeated on each line of the resulting DGrid -- but that value is actually dependent upon security roles on the session...which can't be sent to the front end in a Json. So, my solution is twofold:
First I need to add a "virtual" Json property to the bean... which I can do in the bean with #JsonProperty on a method...
#JsonProperty("validActions")
public String writeValidActions {
return "placeHolderForSerializerToChange";
}
...but it just generates a placeholder. To really generate a valid value,
I need to reference the security role of the session,
which I am very reluctant to code in the above method. (A service call in
the domain bean itself? Seems very wrong.) I
think I should create a custom serializer and put the logic -- and the reference
to the Session.Security role in there. Are my instincts right, not to
inject session info into a domain bean method? And if so, what would such a
custom serializer look like?
Yes, I wouldn't put Session Info in to the domain or access session directly in my domain.
Unless there is a specific reason, you could simply add the logic in your action class.
public String clickDisplayFoos(){
List<Foo> foos = service.getFoos();
for(iterate through foos){
foo.setValidAction(session.hasSecurityRole())
}
String json = objMapper.writeValueAsString(foobeans);
return json;
}
I don't like the idea of setting new values as part of the serialization process. I feel custom serializers are meant to transform the representation of a particular property rather than add new values to a property.

Categories