Force explicit mapping for all target fields with Mapstruct - java

Let's say I have the following objects
public class ClassA {
private String fieldA;
private String fieldB;
}
public class ClassB {
private String fieldA;
private String fieldBWithDifferentName;
private String fieldC;
}
I want to force explicit mappings for all fields in ClassB and force a compile error when a field is not mapped.
I know I can use #Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) for fieldC to force #Mapping(target = "id", ignore = true) so my mapper would look like this
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface Mapper {
#Mapping(target = "fieldBWithDifferentName", source = "fieldB")
#Mapping(target = "fieldC", ignore = true)
ClassB toClassB(final ClassA classA);
}
However, this mapper will automatically map fieldA, because it exists in both classes. And even though I do want to map fieldA, I want the developer to have to do it explicitly and have mapstruct throw an error if it has to resort to the automatic mapping functionality.
Basically, I want to force a #Mapping for every field in the target. Is this possible?

Disabling implicit mapping is possible via #BeanMapping(ignoreByDefault = true) but this annotation must be set on each method. Look at the MapStruct reference guide in ยง 3.1. Basic mappings :
By means of the #BeanMapping(ignoreByDefault = true) the default
behavior will be explicit mapping, meaning that all mappings have to
be specified by means of the #Mapping and no warnings will be issued
on missing target properties. This allows to ignore all fields, except
the ones that are explicitly defined through #Mapping.

Related

One way mapping in Dozer using custom converter

Please note: while I would accept an XML-based solution if that's truly the only way to accomplish what I'm looking for, I would greatly prefer a solution using Dozer's Java API.
I am new to Dozer and am trying to figure out how to use its API. It seems to default to field-level mappings (if the field names match) and to allow for custom mappers and converters in the event that field-level mapping (based on field name) is either not possible or not logical for your application needs.
I have a situation where my app will take a DTO, say, ReportedIssue (an issue reported by a user and sent to my application over HTTP), and an Issue entity (a data entity that will be persisted to a MySQL DB).
Here are my two objects:
#Data
public class ReportedIssue {
private String typeRefId;
private String reporterRefId;
private String info;
}
#Entity
#Table(name = "issues")
#Data
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "issue_ref_id")
private String refId;
#Column(name = "issue_tracking_number")
private String trackingNumber;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_type_id", referencedColumnName = "issue_type_id")
private IssueType type;
#Column(name = "issue_reported_on")
private Date reportedOn;
#OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "issue_reporter_id", referencedColumnName = "account_id")
private Account reporter;
#Column(name = "issue_info")
private String info;
}
So in the application frontend, a user can report an issue. The frontend sends a JSON version of a ReportedIssue to the backend, where that JSON is deserialized into a ReportedIssue DTO bean. Then I need Dozer to convert my ReportedIssue into an Issue entity that I can then easily save to my MySQL DB.
Here is my best attempt:
public class ReportedIssueConverter extends DozerConverter<ReportedIssue, Issue> {
private AuthService authService;
public ReportedIssueConverter(AuthService authService, Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
this.authService = authService;
}
public ReportedIssueConverter(Class<ReportedIssue> prototypeA, Class<Issue> prototypeB) {
super(prototypeA, prototypeB);
}
#Override
public Issue convertTo(ReportedIssue source, Issue destination) {
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setType(IssueUtils.determineType(source));
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
issue.setInfo(destination.getInfo());
return issue;
}
#Override
public ReportedIssue convertFrom(Issue source, ReportedIssue destination) {
throw new UnsupportedOperationException("we currently don't map from issues to reported issues");
}
}
Several concerns here. For one, is such a custom converter even necessary? Or is there a "better" (more standards compliant or using generally-accepted Dozer practices) way to use the Dozer API to perform this conversion? But mainly, this DozerConverter seems to be intended for bi-directional mapping use cases. Whereas, in my application, I will never have an Issue instance and need to map it back to a ReportedIssue DTO instance. So I only need one-way mapping from ReportedIssue --> Issue. Am I using Dozer correctly by throwing an UnsupportedOperationException or is there another interface or API trick I can use to only leverage the one-way mapping I need?
It could actually be done without a custom converter using custom getter methods in your dto class corresponding to fields in Issue. Dozer works by mapping each field in destination class by trying to invoke the getter method of the corresponding name in the source class.
public class ReportedIssue {
// fields.......
public String getRefId() {
UUID.randomUUID().toString()
}
public IssueType getType() {
IssueUtils.determineType(this);
}
// similarly create getters for other required fields.
}
But for reporter field in Issue, you need an AuthService object. I would suggest writing a static method as below:
public static Issue getIssue(AuthService auth, ReportedIssue dto) {
Issue issue = //map using dozer
issue.setReporter(authService.currentUser());
return issue;
}
Gauntham answer will work. Another option:
Implement a com.github.dozermapper.core.BeanFactory
Your custom BeanFactory can handle
Issue issue = new Issue();
issue.setRefId(UUID.randomUUID().toString());
issue.setReportedOn(DateTimeUtils.nowInUTC());
issue.setReporter(authService.currentUser());
Then depending on your preferences, this could also go into the bean factory
issue.setType(IssueUtils.determineType(source));
Or you could handle that separately in the mapping. Something would need to know how to call IssueUtils, so that is either 1) a customer converter or 2) a change to the DTO or entity to have the functionality through a getter or setter.
Finally, this line would be handled in the Dozer Java API mapping
issue.setInfo(destination.getInfo());
Personally, I like Dozer's com.github.dozermapper.core.loader.api.BeanMappingBuilder where you can explicitly tell it how to map 2 beans, specify the bean factory to use and the custom converter for a specific field.
mapping(ReportedIssue.class, Issue.class, oneWay(), wildcard(true), beanFactory(IssueBeanFactory.class.getName()).fields("this", "type", customConverter(IssueTypeConverter.class)
oneWay(), wildcard(boolean), and beanFactory(String) are found in Dozer's TypeMappingOptions and customConverter(Class.class) is found in Dozer's FieldMappingOptions.
oneWay() makes the mapping work only in the direction specified in the BeanMappingBuilder.
wildcard(true) tells Dozer to automatically map matching fields (this is default behavior).

Disable JSR-303 annotation processing in springdoc

How can I disable JSR-303 annotation processing in springdoc for specific fields?
I have the following request class MyRequestTO where the field name is actually optional. The #NotBlank annotation is only applied to the unwrapped JsonNullable. This means the user is allowed to omit the field when sending MyRequestTO but if set it must not be blank. However the open api doc marks the name field as required. Changing the #Schema annotation to #Schema(type = "string", required = false) does not help.
I want to avoid a solution where I have to write my own annotation and make use of org.springdoc.core.customizers.OpenApiCustomiser. The desired solution should also work for other types like JsonNullable<Boolean> annotated with #NotNull.
public class MyRequestTO {
#Schema(type = "string")
#NotBlank
private JsonNullable<String> name = JsonNullable.undefined();
public JsonNullable<String> getName() {
return name;
}
public void setName(JsonNullable<String> name) {
this.name = name;
}
}
Relevant dependencies
implementation "org.openapitools:jackson-databind-nullable:0.2.1"
implementation "org.springdoc:springdoc-openapi-ui:1.5.5"
This is not working as #NotBlank allow null values
The #NotNull class isValid() method is called after the #NotBlank class isValid(), hence forbidding null values.
So you can try with #Pattern with validation of not black string as follows :
#Pattern(regexp = "/^$|\\s+/")
String name
This will allows not null values but not allow empty string
#crizzis solution from the comments to my question works as expected. Fields are no longer marked as required but if supplied have to conform to the annotation constraints.
The JSR-303 (e.g. #NotBlank or #NotNull) annotation belongs in front of the type parameter:
private JsonNullable<#NotBlank String> name = JsonNullable.undefined();
private JsonNullable<#NotNull Boolean> enabled = JsonNullable.undefined();
Then the resulting openAPI docs will mark the fields as "required" : false.

Selectively #JsonIgnore Immutables accessor methods, ONLY during serialization or deserialization using Jackson

This is definitely not a duplicate of Only using #JsonIgnore during serialization, but not deserialization. The problem is the same but in the context of Immutables.
When a model(DTO/DAO) is decorated as an Immutable, I am not able to selectively #JsonIgnore one of the properties during serialization. Suppose that we have a UserDto which is defined as an Immutable as follow
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSerialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
#JsonProperty("id")
#Value.Default
public int getId() {
return 0;
}
#JsonProperty("username")
public abstract String getUsername();
#JsonProperty("email")
public abstract String getEmail();
#JsonProperty("password")
public abstract String getPassword();
}
I believe it is fair to expect that during serialization we would want to ignore the password from the response of the service.
Without using Immutables if we were working with a simple class, then there are many ways to accomplish this. For example - annotate only the getter with #JsonIgnore. Or if possible define a different accessor method (something that doesn't have the get prefix) and only define the regular setter method... and so on.
If I try the same on the Immutables accessor method for the password as shown below:
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSersonIgnoreialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
....
#JsonProperty("password")
#JsonIgnore
public abstract String getPassword();
}
then, the generated ImmutableUserDto adds the #JsonIgnore on both the getter and setter as shown below.
#Generated(from = "UserDto", generator = "Immutables")
#SuppressWarnings({"all"})
#ParametersAreNonnullByDefault
#javax.annotation.Generated("org.immutables.processor.ProxyProcessor")
#Immutable
#CheckReturnValue
public final class ImmutableUserDto extends UserDto {
...
...
private final String password;
...
...
/**
* #return The value of the {#code password} attribute
*/
#JsonProperty("password")
#JsonIgnore
#Override
public String getPassword() {
return password;
}
...
...
...
#Generated(from = "UserDto", generator = "Immutables")
#NotThreadSafe
public static final class Builder {
...
...
private String password;
#JsonProperty("password")
#JsonIgnore
public final Builder setPassword(String password) {
this.password = password;
return this;
}
}
}
Serialization will work as expected. The password attribute will be excluded from the JSON. But when I try to de-serialize, I get the following error:
java.lang.IllegalStateException: Cannot build UserDto, some of the required attributes are not set [password]
Which is obvious as Immutables added the #JsonIgnore to the setter as well.
The documentation isn't of much help. In their Things to be aware of section, it just mentions the following regarding #JsonIgnore
If using #JsonIgnore, you should explicitly make an attribute
non-mandatory. In Immutables, an attribute can be declared as
non-mandatory via #Nullable, Optional or #Value.Default which are all
different in their effect and we do not derive anything automatically.
Using #Nullable or Optional or #Value.Default is not of any use in case of fields like password.
I have gone through the issue list on their GitHub page and there is a similar issue but the user was asking for a slightly different use case and using #Nullable could solve the problem which doesn't work in my case.
I have also tried to use one of the answers here. Still resulted in the same error.
It looked like this is not supported by Immutables library. I have created a new issue myself. Once I get some feedback from users on SOF, I will probably create a sscce.
I had to use the suggestion given by #benarena in this comment. However I had to explicitly specify the value attribute of the property along with the Access attribute.
#JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY) solved the problem.
The Immutable class would look like:
#Value.Immutable
#Value.Style(defaults = #Value.Immutable(copy = false), init = "set*")
#JsonSersonIgnoreialize(as = ImmutableUserDto.class)
#JsonDeserialize(builder = ImmutableUserDto.Builder.class)
public abstract class UserDto {
....
#JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY)
public abstract String getPassword();
}

How to globally apply #JsonIgnoreProperties(value = { "id" }) for all POJO classes of application (jackson api)

I have multiple classes and for all of them I don't want id field to be the part of my output JSON string (serialization). Let say I have 2 classes
#JsonIgnoreProperties(value = { "id" })
public final class Person {
private ObjectId id;
//........
}
#JsonIgnoreProperties(value = { "id" })
public final class Address{
private ObjectId id;
//........
}
Now I don't want to specify #JsonIgnoreProperties(value = { "id" }) manually on all my 1000 classes. Is there any global way to do, so that I can apply this part for all my classes? Very similarly like mapper.setSerializationInclusion(Include.NON_NULL) in below method?
public String serialize(T dataObject) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
String result = mapper.writeValueAsString(dataObject);
return result;
}
One way I tried is to make a super class and applied #JsonIgnoreProperties on top of that (which works). But still I have to write "extends" in every child class which I don't prefer. Is there any way in which I can apply this setting without adding anything additional in my pojo class?
There might be more efficient solution but you might override default marshaller with the one which excludes unwanted fields but keeps all others.

What's wrong with my custom type for JPA/Hibernate?

My custom type is (no default constructor!):
package com.XXX.common;
public class Email implements Serializable {
private String e;
public Email(String str) {
e = str;
}
}
My entity in Hibernate 3.5.6:
package com.XXX.persistence;
import com.XXX.common;
#Entity
#TypeDef(
name = "email",
defaultForType = Email.class,
typeClass = Email.class
)
public class User {
#Id private Integer id;
#Type(type = "email")
private Email email;
}
Hibernate says:
org.hibernate.MappingException: Could not determine type for:
com.XXX.common.Email, at table: user, for columns:
[org.hibernate.mapping.Column(email)]
What am I doing wrong?
My custom type is (no default constructor!) (...)
A custom type must implement one of the interfaces from org.hibernate.usertype (implementing UserType would be enough for your specific example), your Email class is NOT a custom type. In other words, you'll need to create some EmailType class that Hibernate will use to persist a field or property of type Email.
PS: There is IMO not much value at wrapping a String with a class but let's say it was an example.
References
Hibernate Core Reference Guide
5.2.3. Custom value types
Resources
Hibernate CompositeUserType and Annotations
user_type examples on the Hibernate Wiki
typeClass must point to something that extends UserType (this handler class contains the mapping from and to the database).

Categories