Reputation: 2881
I'm trying to write Protocol Buffer (gRPC) mappings.
I have a class structure as follows
public abstract class A;
public class B extends A;
public class C extends A;
public class Source {
A a1;
A a2;
}
and I have a proto file that says:
message Target {
oneOf {
B b1 = 1;
C c1 = 2;
}
oneOf {
B b2 = 3;
C c2 = 4;
}
}
AKA, proto has two VALID fields, but four real fiels, source has only two fields. And types are complicated. Mapstruct will not understand based on type signature what I want, I need to help it with some qualifiers.
I also have it set so that any un-mapped target property is an error.
This leads me to an awkward situation. As far as I understand, mapstruct has ONLY source conditioning. Only a presence check, I can't make it a "whatever I want check".
In an ideal world, I would simply write:
@Mapping(target = "b1", source = "a1", qualifiedByName = "isB")
@Mapping(target = "c1", source = "a1", qualifiedByName = "isC")
@Mapping(target = "b2", source = "a2", qualifiedByName = "isB")
@Mapping(target = "c2", source = "a2", qualifiedByName = "isC")
Target toProto(Source source);
@Condition
@Named("isB")
default boolean hasB(A a) {
return (a instanceof B);
}
@Condition
@Named("isC")
default boolean hasC(A a) {
return (a instanceof C);
}
What I want to generate as code:
if (hasB(source.a1) { b1 = source.a1; }
if (hasC(source.a1) { c1 = source.a1; }
if (hasB(source.a2) { b2 = source.a2; }
if (hasC(source.a2) { c2 = source.a2; }
Which seems to be what a source check would generate any way. But I can't get it to do what I want it to do.
I can't leave it without the name qualifier, it can't know without it.
I have no idea how to name match.
It's giving an error on the qualifier.
If I understand correctly, I can't write hasB1
or hasC1
. It has to be hasXXX
where XXX = source property
. That does not work in this scenario, it would HAVE to be hasYYY
where YYY = target property
. Only other alternative is qualifiers. Qualifiers aren't working. I'm stuck.
What other options do I have? An after mapping, how would that look?
Don't forget, the Target
is a gRpc class, the parameter would be a builder, for an after mapping. But I don't know how I'd write it.
Also, B, and C types are GRPC types, thus they will be mapped with another mapper added in the mapping class with "uses = {...}"
I would rather, if possible, let mapstruct do it's thing, rather than convert the mapper into an abstract, autowire the mappers and do it myself.
But if this could be done with either an @AfterMapping
or another way with a qualifier ... then sure, I guess.
Upvotes: 2
Views: 5775
Reputation: 21403
The use of the qualifiedByName
is for mapping methods. What you are looking for is conditionQualifiedByName
for the isB
and isC
methods.
e.g.
@Mapper(uses = {...})
public interface MyMapper {
@Mapping(target = "b1", source = "a1", conditionQualifiedByName = "isB")
@Mapping(target = "c1", source = "a1", conditionQualifiedByName = "isC")
@Mapping(target = "b2", source = "a2", conditionQualifiedByName = "isB")
@Mapping(target = "c2", source = "a2", conditionQualifiedByName = "isC")
Target toProto(Source source);
@Condition
@Named("isB")
default boolean hasB(A a) {
return (a instanceof B);
}
@Condition
@Named("isC")
default boolean hasC(A a) {
return (a instanceof C);
}
}
Note: In your other mappers there should be methods that will allow you to map A
to B
and A
to C
Note 2: The name of the presence methods are entirely up to you. The pattern hasXXX
is only for presence methods that are present in the type itself. e.g. in this has if Source
had a presence method hasA1
that would check if a1
is present or not.
Upvotes: 7