Jackson include null set by user? - java

I am serializing a POJO using jackosn, and I want that all the values for which the user sets some value irrespective whether it's null or not must be included in the serialization.
So currently:
POJO:
public class YourItem {
public String key;
public String item;
}
Currently when user does:
YourItem item = new YourItem();
item.setKey("abc");
The serialization gives:
{
"key" : "abc"
}
as I configured ObjectMapper as objectMapper.setInclude(Include.NON_NULL)
However now if the user specifically calls the setter and sets the value as null, then I want that item in my serialized string.
So if user does
YourItem item = new YourItem();
item.setKey("abc");
item.setItem(null);
I want in serialzation both key and item values are present like:
{
"key" : "abc",
"item" : null
}
How do I differentiate between the user set null and the default null.
Is there a configuration in ObjectMapper ??

Some people consider using null to be bad practice (The book Clean Code, for instance)
Disregarding that, you cannot differentiate between the default initialization null and a user-set null by language-design
You need some sort of state that tracks if a field has been accessed by a setter. If it hasn't been accessed by a setter and is null, ignore it.
One way to do this is Jackson Filters, which allows you to define various conditions for serializing a field during runtime (your condition being that your setter-access-state indicates that the field was set by a user)
http://www.baeldung.com/jackson-serialize-field-custom-criteria

Related

Perfoming null-checks while collecting with Collectors.groupingBy

I have the following piece of code which groups the given entries (activities, which is Iterable<activity>) based on IDs.
For the final result, I want it to return a Map of ID to Iterables of the entries grouped by that ID.
For example: Map<String, Iterables<activity>>.
Right now, it returns a Map<String, List<activity>>.
stream(activities)
.collect(
groupingBy(
activity -> {
if (activity.getID()) {
return activity.getID();
} else {
return activity.getName();
}
}));
I am unable to figure out a way to do this.
There's no such notion in Java as truthy values, which exists in languages like javascript. I.e. String can't be resolved into boolean automatically (what your code attempts to do).
There are multiple ways of how you can check whether the given value is null and provide an alternative value.
If name attribute is guaranteed to be non-null you can use static method requireNonNullElse() of the Objects utility class:
.collect(Collectors.groupingBy(act -> Objects.requireNonNullElse(act.getID(), act.getName()));
If name attribute is nullable, then you have to provide a default value that will be used in case if both id and name equal to null. Because null key is not allowed with Collectors.groupingBy() and will result in NullPointerException at runtime.
For the case when both field could be null I suggest extracting the logic for obtaining the key into a separate method.
public static String getKey(Action action) {
return action.getID() != null ? action.getID() :
action.getName() != null ? action.getName() : "defaultKey";
}
Which can be used inside the collector like that:
.collect(Collectors.groupingBy(act -> getKey(act)); // or as a method reference MyClass::getKey
Sidenote: by convention, names of classes in Java should start with a capital letter: Student, Employee, Activity.

Can I check in a criteriabuilder chain if value is empty?

I have a criteria builder chain of gets, but they can be null which means they will fail.
example:
predicates.add(cb.equal(house.get("adressInfo").get("streetname"),value)
If for example house.get(adressInfo) is empty I still want it returned with an empty list or just null values for everything is fine.
I only need to filter out for as an example houses with street name "A" but must also include all houses that have an empty adressInfo.
Now I get a null.streetname invalid access error because a house has an adressInfo of null
You could maybe work around this by introducing helper methods like this:
private String getStreet(House house) {
AddressInfo addressInfo = (AdressInfo) house.get("adressInfo");
return addressInfo == null ? null : addressInfo.get("streetname");
}
And then you can do:
predicates.add(cb.equal(getStreet(house), value)

Anyway to unset multiple object fields using a map? [Java]

I have an object with some fields and they have setters. I want to iterate through a map from a request and based on the key name with value null - unset the field by doing something like
field1.set[key](null)
instead of having the request be a list and doing
list.forEach() {
if (list.contains(key)) {
object.setKeyName(null);
}
}
Is this possible? It would be more dynamic vs having to add a new if statement each time a new field is added to the model. Disregard sanitization of the input.
You can do that using Reflection.
for (String fieldName : list) {
Class parameterType; //get the type
object.getClass().getDeclaredMethod("set" + fieldName, parameterType).invoke(object, null);
}

Map null values to default using builder with MapStruct

I want to map field from Source to Target class, and if the source value is null, I would like to convert it to default value based on the data type ("" for strings, 0 for numeric types etc.). For setting the values, I am not using regular setters, but builder (with protobuf, so the names of the methods is newBuilder() and build()).
class Source {
private final String value; // getter
}
class Target {
private final String value;
public static Builder newBuilder() {return new Builder()}
public static class Builder {
public static setValue() {/*Set the field*/}
public static Target build() {/*Return the constructed instance*/}
}
My mapper looks like this:
#Mapper(
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface TargetMapper {
Target map(Source source);
}
The generated mapper implementation with this code calls target.setValue(source.getValue()), instead of performing the null check and setting default value if source returns null. The interesting part is when I add the following annotation to the map method, the null check is present in the implementation.
#Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
Is this a bug in MapStruct with builders, or am I missing some configuration to be ably to set the null mapping as a default policy, instead of duplicating it on all field mappings?
EDIT: For some reason, adding nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS to the class level #Mapper annotation adds the null check, but does not explicitly set the value, just skips the call to setValue. For protobuf, this is okay, since this functionality is in the library, but for other implementations the field would remain null.
#Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
applies to update method (so methods that have the #MappingTarget annotated parameter
There's no real counterpart for regular methods:
1. NullValueMappingStragegy applies to the bean argument itself.
2. NullValueCheckStragegy does perform a check on bean properties, but does not return a default.
Naming is not really brilliant and it has a long history. We still have the intention to align this one day.
A solution would be to use an Object factory creating the builder target object and pre-populate it with default values and then let MapStuct override these one day.
Perhaps you could do something like this:
#Mapper(
// to perform a null check
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface TargetMapper {
Target map(Source source);
}
// to create a pre-defined object (defaults set a-priori). Not sure
// whether this works with builders.. just try
#ObjectFactory
default Target.Builder create() {
Target.Builder builder = Target.newBuilder();
builder.setValueX( "someDefaultValue" );
return builder;
}

Validating an object with properties all of type String checking that they will be able to be parsed to the correct data type

I have an entity that is populated directly from an Excel file so every property is of type String. I am then mapping from this property to an actual entity that has all of the correct data types set using parses with try catch. For example:
InputEntity:
public class ProductInput {
String name;
String color;
String price;
String date;
}
ActualEntity:
public class Product {
String name;
String color;
Double price;
Date date;
}
Prior to doing the actual mapping I would like to log any errors to the database using an Error class I created.
The ultimate goal would be to make sure each value coming from the InputEntity is not null or empty and is the correct type (able to be set in the Actual Entity without any error). I want to use reflection to loop through the fields of the Product class and find the matching field on the ProductInput class. Then checking its value with the correct parse function to make sure it will eventually be able to be set in the Product entity. If there is an error I am going to create an error record that includes the property name that failed and store it in the database saying which input field has a problem.
Is reflection the correct way to go about this? I want the function to be generic enough to handle any classes as the input and actual assuming the properties of the input entity will always be string and the property names will match.
I was thinking somewhere along the lines of:
public validateFields(Class<T> inputClass, Class<T> destinationClass) {
Field[] inputFields = inputClass.getDeclaredFields();
Field[] destinationFields = destinationClass.getDeclaredFields();
for (Field field: destinationFields) {
// Check for same field in inputClass
// If it exists confirm value of field is not null or empty
// Additionally confirm the value of field can be parsed to the type of the destinationField
// Create error entity with property name if there is a problem
}
}

Categories

Resources