Convert specific fields before mapping in controller - java

I need to convert two specific fields before mapping to Dto class.
Data i get from form:
userName: test
password: 123
active: N
enabled: N
external: N
id: -1
Dto class:
#Getter #Setter
public class UserDto {
protected Integer id;
protected String userName;
protected String password;
protected boolean active = false;
protected boolean enabled = true;
protected String external;
}
Fields active and enabled are a boolean type, but from form i get "Y" or "N" string values. Cause of it, i got an error Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'active'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [N]
I've read about the Сonverter<T,V> and PropertyEditors, but I'm not sure if they work for me and how can i apply it only for 2 specific fields.
Controller:
#RequestMapping(value = "/user/save",method = RequestMethod.POST)
public ResponseEntity<String> saveUser(#ModelAttribute UserDto userDto, BindingResult bindingResultM, HttpServletRequest req) {
List<FieldError> errors = bindingResultM.getFieldErrors();
for (FieldError error : errors) {
log.error("bindingResultM: " + error.getObjectName() + " - " + error.getDefaultMessage());
}
try {
userService.saveUserDto(userDto);
return new ResponseEntity<>("OK", HttpStatus.OK);
} catch (Exception e){
throw new ResponseException(e.getMessage(), HttpStatus.METHOD_FAILURE);
}
}

Because you are saying you need to get data from the form to populate the UserDTO object, which is a process of translate String type to other type.
So, you need to use "org.springframework.core.convert.converter.Converter", "java.beans.PropertyEditor", or "org.springframework.format.Formatter". All of three can do the job of translating String type to other type or other type to String type.
But PropertyEditor is too complicated, so usually we always use Converter or Formatter, and these two can be set in the same method "default void addFormatters(FormatterRegistry registry) {}" of the "WebMvcConfigurer" interface.
But that's for the whole application, you only want the translation happen for "active" and "enabled" fields of UserDTO.
Then you need to use the "org.springframework.web.bind.annotation.InitBinder", which can be defined inside the Controller for specific method parameter. The "userDTO" of the "#InitBinder" means the data binder is only used for the "userDTO" method parameter inside this Controller. (This name is case-sensitive, so if you "userDto" in the method, you need to change it to userDto in the #InitBinder annotation.)
Inside this method, you can specify the Formatter only for "active" and "enabled" fields, by using this method:
public void addCustomFormatter(Formatter<?> formatter, String... fields){}
Of course you can specify the Converter for these two fields, but there is no direct method for it in the "WebDataBinder" class. So, it is eaiser to use the Formatter.
#InitBinder("userDTO")
public void forUserDto(WebDataBinder binder) {
binder.addCustomFormatter(new BooleanFormatter(), "active", "enabled");
}
Here is the BooleanFormatter class:
public class BooleanFormatter implements Formatter<Boolean>{
#Override
public String print(Boolean object, Locale locale) {
if (object) {
return "Y";
}
return "N";
}
#Override
public Boolean parse(String text, Locale locale) throws ParseException {
if (text.equals("Y")) {
return true;
}
return false;
}
}

On your DTO you can annotate your boolean fields with :
#JsonDeserialize(
using = BooleanDeserializer.class
)
protected boolean enabled;
And create the deserializer :
public class BooleanDeserializer extends JsonDeserializer<Boolean> {
public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String booleanValue = StringUtils.trimToEmpty(jsonParser.getText().trim());
return BooleanUtils.toBoolean(booleanValue, "Y", "N");
}
}

Related

Jackson: How to edit existing property to the JSON without modifying the POJO?

I need to edit the name of "existing field" in POJO instead of adding "extra_field". Is it possible with the approach referenced link below?
Please note I do not want to use #JsonProperty annotation.
Requirement is, I have a POJO and want to use different field name every time without change in POJO. For example I have a field c_id in POJO and some times it need to write as cust_id and another time it would be my_id.
Also note I cannot change implementation of POJO as it is already used in several modules and have generic implementation.
POJO Example:
class MyPojo {
String id;
// getter and setters
}
Expected output can be the following: (name of field can be changed)
{"cust_id": "123"}
{"my_id": "123"}
Mixins
The easiest way to modify the output of Jackson without adding annotations to the original POJO is using mixins.
Just define a mixin-class with the necessary annotations and indicate to Jackson that you want to use the mixin when serializing the original object.
private static class MyPojoMixin {
#JsonProperty("cust_id")
private String id;
}
public String serializeWithMixin(MyPojo p) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(MyPojo.class, MyPojoMixin.class);
return mapper.writeValueAsString(p);
}
Custom property naming strategy
If you need to programmatically change the field-name, you might not be able to use the mixin solution. You could then use a custom PropertyNamingStrategy:
public class IdRenamingStrategy extends PropertyNamingStrategy {
private final PropertyNamingStrategy inner;
private final String newIdPropertyName;
public IdRenamingStrategy(String newIdPropertyName) {
this(PropertyNamingStrategy.LOWER_CAMEL_CASE, newIdPropertyName);
}
public IdRenamingStrategy(PropertyNamingStrategy inner, String newIdPropertyName) {
this.inner = inner;
this.newIdPropertyName = newIdPropertyName;
}
private String translate(String propertyName) {
if ("id".equals(propertyName)) {
return newIdPropertyName;
} else {
return propertyName;
}
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return inner.nameForField(config, field, translate(defaultName));
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return inner.nameForGetterMethod(config, method, translate(defaultName));
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return inner.nameForSetterMethod(config, method, translate(defaultName));
}
#Override
public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
return inner.nameForConstructorParameter(config, ctorParam, translate(defaultName));
}
}
This can be used like this:
public String serializeWithPropertyNamingStrategy(MyPojo p) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new IdRenamingStrategy("cust_id"));
return mapper.writeValueAsString(p));
}

Spring map Enum in #RequestBody

In my controller I made an endpoint that allows status change:
#RequestMapping(value = "{ids}" + "/" + "status", method = RequestMethod.PUT)
public ResponseEntity<Void> changeStatus(#PathVariable final List<Integer> ids,
#NotNull #RequestBody final String status) {
deviceService.updateStatus(ids, status);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
And enum looks like this:
public enum DeviceStatus {
ACTIVE, INACTIVE, DELETED, ARCHIVED;
#JsonCreator
public static DeviceStatus parseWithValidation(String status) {
final String upperCaseStatus = status.toUpperCase();
if (exists(upperCaseStatus)) {
return DeviceStatus.valueOf(upperCaseStatus);
} else {
throw new UnsupportedStatusException();
}
}
private static boolean exists(final String upperCaseStatus) {
return Arrays.stream(values()).anyMatch(c -> c.name().equals(upperCaseStatus));
}
}
But Device domain object has a field Status of type DeviceStatus, so how should change status:
public void updateStatus(final List<Integer> ids, final String status) {
getByIds(ids).forEach(device -> {
device.setStatus(status);
update(device);
});
}
But there is a problem with device.setStatus(status);. I can use parseWithValidation but it doesn't make sense, because it is already done. Someone gives me {"status":"INACTIVE"} How should I parse this enum ?
EDIT: updated see comments
Your request body is an object with one field named status of type DeviceStatus, so you can probably use your Device class
So:
class Device {
// will be validated in the controller
private String status;
// getter, setter, etc
}
// with:
public enum DeviceStatus {
ACTIVE, INACTIVE, DELETED, ARCHIVED;
}
and #RequestBody Foo foo in the controller method signature:
public ResponseEntity<Void> changeStatus(#PathVariable final List<Integer> ids, #NotNull #RequestBody final Device device) {
try {
deviceService.updateStatus(ids, DeviceStatus.valueOf(device.getStatus()));
} catch(IllegalArgumentException ex) {
// throw some custom exception. device.getStatus() was invalid
} catch(NullPointerException ex) {
// throw some custom exception. device.getStatus() was null
}
// ...

Play Java validation fail for Set and Map

I use Play 2.5.12 to provide a web service to create an object and validate its attributes. Here is a simplified code of what I want to do:
public class Example {
private String lastName;
private List<String> firstNames;
private Map<String, Integer> vehicles;
public Example() {}
public String validate() {
if (vehicles.get("Ferrari") != null)
return "Liar!";
return null;
}
#Override
public String toString() {
return new ToStringBuilder(this).append(lastName).append(firstNames).append(vehicles).toString();
}
// getters and setters
}
public class ExampleController extends Controller {
private FormFactory formFactory;
#Inject
public ExampleController(FormFactory formFactory) {
this.formFactory = formFactory;
}
#BodyParser.Of(BodyParser.Json.class)
public Result createExample() {
Example exampleFromJackson = Json.fromJson(request().body().asJson(), Example.class);
System.out.println(exampleFromJackson.toString());
Example exampleFromForm = formFactory.form(Example.class).bindFromRequest().get();
System.out.println(exampleFromForm.toString());
// etc
return created();
}
}
If I call the web service with a body like this:
{
"lastName": "Martin",
"firstNames": ["Robert", "Cecil"],
"vehicles": {
"BMW": 1,
"Seat": 1
}
}
The deserialized object by jackson prints correctly then this error occurs:
org.springframework.beans.InvalidPropertyException: Invalid property 'firstNames[0]' of bean class [models.Example]: Property referenced in indexed property path 'firstNames[0]' is neither an array nor a List nor a Map; returned value was [[]]
If I replace the Set by a List, I can avoid this error and then I obtain the following:
models.Example#68d2162a[Martin,[Robert, Cecil],{BMW=1, Seat=1}]
models.Example#29ef1c11[Martin,[Robert, Cecil],{}]
The deserialized object by jackson is correct but the one obtained by the request isn't so I can't do a correct validation.
So my questions are:
- Why Play doesn't let me use a Set?
- Why is the Map not retrieved?

How to specify object custom serialization in ORMLite?

I would like to store some field of type ParentClass as json string into my database. I don't want to use Serializable interface and DataType.SERIALIZABLE cause it ties with full class name of serialized class.
So I'm using the following code:
class ParentClass {
#DatabaseField(persisterClass = MyFieldClassPersister.class)
private MyFieldClass myField;
}
where persister class a kind of:
public class MyFieldClassPersister extends StringType {
private static final MyFieldClassPersister singleTon = new MyFieldClassPersister();
public static MyFieldClassPersister getSingleton() {
return singleTon;
}
protected MyFieldClassPersister() {
super(SqlType.STRING, new Class<?>[0]);
}
#Override
public Object parseDefaultString(FieldType fieldType, String defaultStr) {
return jsonStringToObject(defaultStr);
}
#Override
public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException {
String string = results.getString(columnPos);
return jsonStringToObject(string);
}
private static MyFieldClass jsonStringToObject(String string) {
// json to object conversion logic
}
}
Here are two issues I've met:
I didn't get how to specify custom convertion from object to string. Seems that ORMLite calls Object.toString() in order to get string representation of the object. It would be great to have some method in Persister in which I could specify how to convert Object to string (json in my case). Yes, I can override toString() method in MyFieldClass, but it is more convenient to perform conversion in Persister. Is there any method I could override in order to specify convertion from model object to db-object?
If I mark my custom field type as String type:
class ParentClass {
#DatabaseField(dataType = DataType.STRING, persisterClass = MyFieldClassPersister.class)
private MyFieldClass myField;
}
then ormlite crashes when saving object with the following message:
java.lang.IllegalArgumentException: Field class com.myapp.venue.MyFieldClass for
field FieldType:name=myField,class=ParentClass is not valid for type
com.j256.ormlite.field.types.StringType#272ed83b, maybe should be
class java.lang.String
It doesn't crash if I omit dataType specification. Can I avoid this crash in some way? It seems to me that it's better to specify types explicitly.
So basically your persister should be implemented in the next way:
public class MyFieldClassPersister extends StringType {
private static final MyFieldClassPersister INSTANCE = new MyFieldClassPersister();
private MyFieldClassPersister() {
super(SqlType.STRING, new Class<?>[] { MyFieldClass.class });
}
public static MyFieldClassPersister getSingleton() {
return INSTANCE;
}
#Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
MyFieldClass myFieldClass = (MyFieldClass) javaObject;
return myFieldClass != null ? getJsonFromMyFieldClass(myFieldClass) : null;
}
#Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
return sqlArg != null ? getMyFieldClassFromJson((String) sqlArg) : null;
}
private String getJsonFromMyFieldClass(MyFieldClass myFieldClass) {
// logic here
}
private MyFieldClass getMyFieldClassFromJson(String json) {
// logic here
}
}
You should register it in onCreate method of your OrmLiteSqliteOpenHelper class
#Override
public void onCreate(SQLiteDatabaseHolder holder, ConnectionSource connectionSource) {
try {
//...
DataPersisterManager
.registerDataPersisters(MyFieldClassPersister.getSingleton());
} catch (SQLException e) {
// log exception
}
}
And then you can use it in your model like this:
#DatabaseField(persisterClass = MyFieldClassPersister.class, columnName = "column_name")
protected MyFieldClass myFieldClass;
Don't register the persister adapter in the onCreate() method. This method only gets called when your database is first created. You should add this somewhere else, like your constructor or onOpen() method.

Setting default values to null fields when mapping with Jackson

I am trying to map some JSON objects to Java objects with Jackson. Some of the fields in the JSON object are mandatory(which I can mark with #NotNull) and some are optional.
After the mapping with Jackson, all the fields that are not set in the JSON object will have a null value in Java. Is there a similar annotation to #NotNull that can tell Jackson to set a default value to a Java class member, in case it is null?
Edit:
To make the question more clear here is some code example.
The Java object:
class JavaObject {
#NotNull
public String notNullMember;
#DefaultValue("Value")
public String optionalMember;
}
The JSON object can be either:
{
"notNullMember" : "notNull"
}
or:
{
"notNullMember" : "notNull",
"optionalMember" : "optional"
}
The #DefaultValue annotations is just to show what I am asking. It's not a real annotation. If the JSON object is like in the first example I want the value of the optionalMember to be "Value" and not null. Is there an annotation that does such a thing?
There is no annotation to set default value.
You can set default value only on java class level:
public class JavaObject
{
public String notNullMember;
public String optionalMember = "Value";
}
Only one proposed solution keeps the default-value when some-value:null was set explicitly (POJO readability is lost there and it's clumsy)
Here's how one can keep the default-value and never set it to null
#JsonProperty("some-value")
public String someValue = "default-value";
#JsonSetter("some-value")
public void setSomeValue(String s) {
if (s != null) {
someValue = s;
}
}
Use the JsonSetter annotation with the value Nulls.SKIP
If you want to assign a default value to any param which is not set in json request then you can simply assign that in the POJO itself.
If you don't use #JsonSetter(nulls = Nulls.SKIP) then the default value will be initialised only if there is no value coming in JSON, but if someone explicitly put a null then it can lead to a problem. Using #JsonSetter(nulls = Nulls.SKIP) will tell the Json de-searilizer to avoid null initialisation.
Value that indicates that an input null value should be skipped and the default assignment is to be made; this usually means that the property will have its default value.
as follow:
public class User {
#JsonSetter(nulls = Nulls.SKIP)
private Integer Score = 1000;
...
}
You can create your own JsonDeserializer and annotate that property with #JsonDeserialize(using = DefaultZero.class)
For example: To configure BigDecimal to default to ZERO:
public static class DefaultZero extends JsonDeserializer<BigDecimal> {
private final JsonDeserializer<BigDecimal> delegate;
public DefaultZero(JsonDeserializer<BigDecimal> delegate) {
this.delegate = delegate;
}
#Override
public BigDecimal deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return jsonParser.getDecimalValue();
}
#Override
public BigDecimal getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return BigDecimal.ZERO;
}
}
And usage:
class Sth {
#JsonDeserialize(using = DefaultZero.class)
BigDecimal property;
}
There is a solution, if you use Lombok's Builder annotation, you can combine Lombok with Jackson via the #Jacksonized annotation.
Without this, the combination of Lombok and Jackson is not working for this.
Via adding the #Builder.Default on the property you are then able to set default values.
#Value
#Builder
#Jacksonized
public class SomeClass {
String field1;
#Builder.Default
String field2 = "default-value";
}
So, in the incoming json request, if the field2 is not specified, then the Builder.default annotation will allow the Builder interface to set the specified default-value into the property, if not, the original value from the request is set into that.
Looks like the solution is to set the value of the properties inside the default constructor. So in this case the java class is:
class JavaObject {
public JavaObject() {
optionalMember = "Value";
}
#NotNull
public String notNullMember;
public String optionalMember;
}
After the mapping with Jackson, if the optionalMember is missing from the JSON its value in the Java class is "Value".
However, I am still interested to know if there is a solution with annotations and without the default constructor.
Make the member private and add a setter/getter pair.
In your setter, if null, then set default value instead.
Additionally, I have shown the snippet with the getter also returning a default when internal value is null.
class JavaObject {
private static final String DEFAULT="Default Value";
public JavaObject() {
}
#NotNull
private String notNullMember;
public void setNotNullMember(String value){
if (value==null) { notNullMember=DEFAULT; return; }
notNullMember=value;
return;
}
public String getNotNullMember(){
if (notNullMember==null) { return DEFAULT;}
return notNullMember;
}
public String optionalMember;
}
Another option is to use InjectableValues and #JacksonInject. It is very useful if you need to use not always the same value but one get from DB or somewhere else for the specific case. Here is an example of using JacksonInject:
protected static class Some {
private final String field1;
private final String field2;
public Some(#JsonProperty("field1") final String field1,
#JsonProperty("field2") #JacksonInject(value = "defaultValueForField2",
useInput = OptBoolean.TRUE) final String field2) {
this.field1 = requireNonNull(field1);
this.field2 = requireNonNull(field2);
}
public String getField1() {
return field1;
}
public String getField2() {
return field2;
}
}
#Test
public void testReadValueInjectables() throws JsonParseException, JsonMappingException, IOException {
final ObjectMapper mapper = new ObjectMapper();
final InjectableValues injectableValues =
new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
mapper.setInjectableValues(injectableValues);
final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
assertEquals(actualValueMissing.getField1(), "field1value");
assertEquals(actualValueMissing.getField2(), "somedefaultValue");
final Some actualValuePresent =
mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
assertEquals(actualValuePresent.getField1(), "field1value");
assertEquals(actualValuePresent.getField2(), "field2value");
}
Keep in mind that if you are using constructor to create the entity (this usually happens when you use #Value or #AllArgsConstructor in lombok ) and you put #JacksonInject not to the constructor but to the property it will not work as expected - value of the injected field will always override value in json, no matter whether you put useInput = OptBoolean.TRUE in #JacksonInject. This is because jackson injects those properties after constructor is called (even if the property is final) - field is set to the correct value in constructor but then it is overrided (check: https://github.com/FasterXML/jackson-databind/issues/2678 and https://github.com/rzwitserloot/lombok/issues/1528#issuecomment-607725333 for more information), this test is unfortunately passing:
protected static class Some {
private final String field1;
#JacksonInject(value = "defaultValueForField2", useInput = OptBoolean.TRUE)
private final String field2;
public Some(#JsonProperty("field1") final String field1,
#JsonProperty("field2") #JacksonInject(value = "defaultValueForField2",
useInput = OptBoolean.TRUE) final String field2) {
this.field1 = requireNonNull(field1);
this.field2 = requireNonNull(field2);
}
public String getField1() {
return field1;
}
public String getField2() {
return field2;
}
}
#Test
public void testReadValueInjectablesIncorrectBehavior() throws JsonParseException, JsonMappingException, IOException {
final ObjectMapper mapper = new ObjectMapper();
final InjectableValues injectableValues =
new InjectableValues.Std().addValue("defaultValueForField2", "somedefaultValue");
mapper.setInjectableValues(injectableValues);
final Some actualValueMissing = mapper.readValue("{\"field1\": \"field1value\"}", Some.class);
assertEquals(actualValueMissing.getField1(), "field1value");
assertEquals(actualValueMissing.getField2(), "somedefaultValue");
final Some actualValuePresent =
mapper.readValue("{\"field1\": \"field1value\", \"field2\": \"field2value\"}", Some.class);
assertEquals(actualValuePresent.getField1(), "field1value");
// unfortunately "field2value" is overrided because of putting "#JacksonInject" to the field
assertEquals(actualValuePresent.getField2(), "somedefaultValue");
}
Another approach is to use JsonDeserializer, e.g.:
public class DefaultValueDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
return jsonParser.getText();
}
#Override
public String getNullValue(DeserializationContext ctxt) {
return "some random value that can be different each time: " + UUID.randomUUID().toString();
}
}
and then annotate a field like that:
public class Content {
#JsonDeserialize(using = DefaultValueDeserializer.class)
private String someField;
...
}
keep in mind that you can use attributes in getNullValue(DeserializationContext ctxt) passed using
mapper.reader().forType(SomeType.class).withAttributes(singletonMap("dbConnection", dbConnection)).readValue(jsonString);
like that:
#Override
public String getNullValue(DeserializationContext ctxt) {
return ((DbConnection)ctxt.getAttribute("dbConnection")).getDefaultValue(...);
}
Hope this helps to someone with a similar problem.
P.S. I'm using jackson v. 2.9.6
I had a similar problem, but in my case the default value was in database. Below is the solution for that:
#Configuration
public class AppConfiguration {
#Autowired
private AppConfigDao appConfigDao;
#Bean
public Jackson2ObjectMapperBuilder builder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.deserializerByType(SomeDto.class,
new SomeDtoJsonDeserializer(appConfigDao.findDefaultValue()));
return builder;
}
Then in SomeDtoJsonDeserializer use ObjectMapper to deserialize the json and set default value if your field/object is null.
There are already a lot of good suggestions, but here's one more. You can use #JsonDeserialize to perform an arbitrary "sanitizer" which Jackson will invoke post-deserialization:
#JsonDeserialize(converter=Message1._Sanitizer.class)
public class Message1 extends MessageBase
{
public String string1 = "";
public int integer1;
public static class _Sanitizer extends StdConverter<Message1,Message1> {
#Override
public Message1 convert(Message1 message) {
if (message.string1 == null) message.string1 = "";
return message;
}
}
}
You can also apply #JsonInclude(Include.NON_NULL) to the entire class using Jackson > 2. This will ignore null fields.
Value that indicates that only properties with non-null values are to be included.
#JsonInclude(Include.NON_NULL)
class Foo
{
String bar;
}
https://stackoverflow.com/a/11761975/5806870

Categories