mapstruct wrapper type and generics - java

I am trying to map JsonNullable<List<ChildRequestTO> to Nullable<List<ChildRequestDO>> (see full code below) with mapstruct 1.4.2.Final and I am facing the following error: error: Nullable<List<ChildRequestDO>> does not have an accessible constructor. If I add a constructor for Nullable like
public Nullable(T value) {
this.value = value;
this.isPresent = true;
}
then I get the following error error: Unmapped target property: "value". Mapping from property "JsonNullable<List<ChildRequestTO>> products" to "Nullable<List<ChildRequestDO>> products".
How do I map complex wrapped types in a generic way?
The following mapping code (part of ChildRequestMapper class and applied in ObjectRequestMapper) solves the problem but I want to solve it in a more generic way:
#Named("mappingHelper")
default Nullable<List<ChildRequestDO>> customMapToDOs(JsonNullable<List<ChildRequestTO>> input) {
if (JsonNullable.undefined().equals(input)) {
return Nullable.undefined();
}
if (input.get() == null) {
return Nullable.of(null);
}
var output= input.get()
.stream()
.map(this::mapToDO)
.collect(Collectors.toList());
return Nullable.of(output);
}
Changing the NullableMapper to the code below does not work/compile because I do not know how to tell mapstruct to look for the appropriate mapper to map from T to X.
public static <T, X> Nullable<X> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
Full code:
#Mapper(
unmappedTargetPolicy = ReportingPolicy.ERROR,
uses = {ChildRequestMapper.class, NullableMapper.class}
)
public interface ObjectRequestMapper {
#Mapping(target = "slots", source = "slots", qualifiedByName = "mapToSlotDOs")
ModifyObjectRequestDO mapToDO(ModifyObjectRequestTO input);
}
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface ChildRequestMapper {
ChildRequestDO mapToDO(ChildRequestTO input);
}
public class NullableMapper {
public static <T> Nullable<T> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if (jsonNullable.isPresent()) {
return Nullable.of(jsonNullable.get());
}
return Nullable.undefined();
}
}
public class ModifyObjectRequestTO {
private JsonNullable<String> name = JsonNullable.undefined();
private JsonNullable<List<ChildRequestTO>> children = JsonNullable.undefined();
}
public class ModifyObjectRequestDO {
private Nullable<String> name = Nullable.undefined();
private Nullable<List<ChildRequestDO>> children = Nullable.undefined();
}
public class Nullable<T> {
private static final Nullable<?> UNDEFINED = new Nullable<>(null, false);
private final T value;
private final boolean isPresent;
private Nullable(T value, boolean isPresent) {
this.value = value;
this.isPresent = isPresent;
}
public static <T> Nullable<T> undefined() {
#SuppressWarnings("unchecked")
Nullable<T> t = (Nullable<T>) UNDEFINED;
return t;
}
public static <T> Nullable<T> of(T value) {
return new Nullable<T>(value, true);
}
public T get() {
if (!isPresent) {
throw new NoSuchElementException("Value is undefined");
}
return value;
}
public boolean isPresent() {
return isPresent;
}
}

Related

Java. Factory creation should return Generic class

Could someone please help.
I would like to create a Factory, which would return specific class.
Having problem on the line - filterFactory.getFilter(myColumn.getType()).setMin(5);
There is an error: Cannot resolve method 'setMin' in 'Object'
public enum Columns {
Name(ColumnType.String),
Age(ColumnType.Numeric);
private final ColumnType type;
Columns(ColumnType type) {
this.type = type;
}
public ColumnType getType() {
return type;
}
}
public enum ColumnType {
String,
Numeric
}
public class NumericFilter extends ColumnFilter {
public void setMin(int min) {
System.out.println("min is set" + min);
}
}
public class StringFilter extends ColumnFilter {
public void setFilter(String filter) {
System.out.println("filter is set to:" + filter);
}
}
public class ColumnFilterFactory {
public <T> T getFilter(ColumnType type) {
if (type == null) {
return null;
}
if (type == ColumnType.String) {
return (T) new StringFilter();
} else if (type == ColumnType.Numeric) {
return (T) new NumericFilter();
}
return null;
}
}
public class BasicTest {
public static void main(String[] args) {
Columns myColumn = Columns.Age;
ColumnFilterFactory filterFactory = new ColumnFilterFactory();
filterFactory.getFilter(myColumn.getType()).setMin(5);
}
}
There's no way the compiler can know what type the factory is going to return, so you need to give it a little help. For example by using an intermediate variable:
NumericFilter nf = filterFactory.getFilter(myColumn.getType());
nf.setMin(5);

How to find method of child classes?

I'm working on building some classes which will represent data to be converted to JSON.
The values of these fields could be of various types (might be an int, might be a boolean).
This is an example of what I have so far (minimum reproducible example):
import javax.json.Json;
import javax.json.JsonObjectBuilder;
abstract class AttributeValue {}
class AttributeValueInt extends AttributeValue {
private int value;
AttributeValueInt( int value ) {this.value = value;}
int getValue() { return value; }
}
class AttributeValueBool extends AttributeValue {
private boolean value;
AttributeValueBool( boolean value ) {this.value = value;}
boolean getValue() { return value; }
}
class Attribute {
private AttributeValue attrValue;
Attribute( AttributeValue attrValue ) { this.attrValue = attrValue; }
AttributeValue getAttrValue() { return attrValue; }
}
class Example {
void getJSON( Attribute attribute ) {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add( "key", attribute.getAttrValue().getValue() );
// Cannot resolve method 'getValue()'
}
}
i.e. AttributeValueInt and AttributeValueBool extend the abstract class AttributeValue. value (towards the bottom) may be either an AttributeValueInt or an AttributeValueBool.
Since both these classes implement a getValue method, I was hoping that attribute.getAttrValue().getValue() would resolve to either an int or a boolean accordingly.
The full error is this:
Error:(39, 61) java: cannot find symbol
symbol: method getValue()
location: class com.fanduel.brazepublishing.AttributeValue
How can I get this working? I thought about adding an abstract getValue method to the abstract class, but what would its return type be?
You can use a generic for that. Here an example:
abstract class AttributeValue<AttributeType> {
AttributeType value;
AttributeType getValue() {
return value;
}
}
class AttributeValueInt extends AttributeValue<Integer> {
AttributeValueInt(int value) {
this.value = value;
}
}
class AttributeValueBool extends AttributeValue<Boolean> {
AttributeValueBool(boolean value) {
this.value = value;
}
}
class Main {
static String getJson(AttributeValue<?> attribute) {
return "key: " + attribute.getValue();
}
public static void main(String[] args) {
AttributeValue<?> attributeInt = new AttributeValueInt(42);
AttributeValue<?> attributeBool = new AttributeValueBool(true);
System.out.println(getJson(attributeInt));
System.out.println(getJson(attributeBool));
}
}
Here you can call getValue on an AttributeValue instance as the type of the attribute is specified by <AttributeType>. One drawback is that you can't use primitive types anymore.
There are many ways to solve this. But since you have mentioned the AttributeValue can be of any type, you can simply use Map<K, V>
// String - key
// Object - Any value type
Map<String, Object> jsonFields = new HashMap<>();
// populate the Map
JsonObjectBuilder builder = Json.createObjectBuilder();
for(Map.Entry<String, Object> currentEntry : jsonFields.entrySet()) {
builder.add(currentEntry.getKey(), currentEntry.getValue());
}
With this way, you would have separate keys for each mapped values. Or if you still wanna stick with AttributeValue implementations, you can acheive like below. Since, Object is the parent class for all the Java classes, this will work.
abstract class AttributeValue {
public abstract Object getValue();
}
class AttributeValueInt extends AttributeValue {
private int value;
public AttributeValueInt(int value) {this.value = value;}
#Override
public Object getValue() { return value; }
}
class AttributeValueBool extends AttributeValue {
private boolean value;
public AttributeValueBool(boolean value) {this.value = value;}
#Override
public Object getValue() { return value; }
}
public class Main {
static void getJSON( Attribute attribute ) {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add( "key", attribute.getAttrValue().getValue() );
// rest of your code
}
public static void main(String[] args) {
// your code
}
}

mapping of non-iterable to iterable in mapstruct

I am trying to map a non-iterable value i.e. String to a list of string using mapstruct.
So I am using
#Mapping(target = "abc", expression = "java(java.util.Arrays.asList(x.getY().getXyz()))")
Here abc is List<String>
xyz is a String
But for this i need to check for null explicitly.
Is there any better way of maaping a non-iterable to iterable by converting non iterable to iterable.
Here is an example for non iterable-to-iterable:
public class Source {
private String myString;
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
}
public class Target {
private List<String> myStrings;
public List<String> getMyStrings() {
return myStrings;
}
public void setMyStrings(List<String> myStrings) {
this.myStrings = myStrings;
}
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface FirstElement {
}
public class NonIterableToIterableUtils {
#FirstElement
public List<String> first(String in ) {
if (StringUtils.isNotEmpty(in)) {
return Arrays.asList(in);
} else {
return null;
}
}
}
#Mapper( uses = NonIterableToIterableUtils.class )
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
#Mappings( {
#Mapping( source = "myString", target = "myStrings", qualifiedBy = FirstElement.class )
} )
Target toTarget( Source s );
}
public class Main {
public static void main(String[] args) {
Source s = new Source();
s.setMyString("Item");
Target t = SourceTargetMapper.MAPPER.toTarget( s );
System.out.println( t.getMyStrings().get(0));
}
}
There is a iterable-to-non-iterable example in the MapStruct examples repository. Addtionally there is a pending pull request for non-iterable-to-iterable.
In a nutshell you can use a custom method that would do the mapping. You can also use #Qualifier to have more granural control
Add an empty default method in the mapper, e.g. AnimalMapper.toLions(), and overwrite the default method in a mapper decorator, e.g. AnimalMapperDecorator. It works in my test.
#Repository
#Mapper(componentModel = "spring")
#DecoratedWith(AnimalMapperDecorator.class)
public interface AnimalMapper {
default List<Lion> toLions(Jungle jungle) {
return null;
}
}
public abstract class AnimalMapperDecorator implements AnimalMapper {
#Autowired
#Qualifier("delegate")
private AnimalMapper delegate;
#Override
public List<Lion> toLions(Jungle jungle) {
List<Lion> lions = new ArrayList<>();
Lion king = getKing(jungle);
Lion queen = getQueen(jungle);
Lion leo = getLeo(jungle);
lions.add(king); lions.add(queen); lions.add(leo);
return lions;
}
}
class Test {
#Autowired
private AnimalMapper animalMapper;
public void test() {
Jungle jungle = generateJungle();
List<Lion> lions = animalMapper.toLions(jungle);
// Assert lions
}
}

Java Jackson: deserialize Generic type

I am trying to deserialize a class TrainingData as shown below. TrainingData uses gnerics and one of its attributes is declared in its super class. When I try deserializing a JSON string to construct a TrainingData object, I get the following exception:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class TrainingData]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: /home/shubham/workspace_smart_devices/ContextEngine/target/classes /training.json; line: 2, column: 2]
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:483)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:350)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2395)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1549)
at main(IEStarter.java:52)
I use the following JSON String to construct the object:
{
"UserCommand":
{
"userId" : "1",
"text" : "Play metallica",
"perceptualContext" : {},
"behavorialContext" : {}
},
"Context" :
{
"musicBand" : "metallica",
"musicArtist" : null,
"musicAlbum" : null,
"musicSong" : null,
"musicGenre" : null,
"klass" : "PlayMusicContext"
}
}
TrainingData.java
public class TrainingData<T extends Context> extends Data {
private T context;
public TrainingData (UserCommand u, T context) {
super(u);
this.context = context;
}
public Klass getKlass() {
return context.getKlass();
}
}
Data.java:
public class Data {
private UserCommand userCommand;
public Data(UserCommand userCommand) {
this.userCommand = userCommand;
}
}
PlayMusicContext.java
public class PlayMusicContext extends Context {
private final String musicBand;
private final String musicArtist;
private final String musicAlbum;
private final String musicSong;
private final String musicGenre;
public static class Builder {
private String musicBand;
private String musicArtist;
private String musicAlbum;
private String musicSong;
private String musicGenre;
public Builder() {}
public Builder musicBand(String val) { musicBand = val; return this; }
public Builder musicArtist(String val) { musicArtist = val; return this; }
public Builder musicAlbum(String val) { musicAlbum = val; return this; }
public Builder musicSong(String val) { musicSong = val; return this; }
public Builder musicGenre(String val) { musicGenre = val; return this; }
public PlayMusicContext build(Klass klass) {
return new PlayMusicContext(this, klass);
}
}
private PlayMusicContext(Builder builder, Klass klass) {
super(klass);
this.musicBand = builder.musicBand;
this.musicArtist = builder.musicArtist;
this.musicAlbum = builder.musicSong;
this.musicSong = builder.musicSong;
this.musicGenre = builder.musicGenre;
}
}
Context.java:
public abstract class Context {
private Klass klass;
public Context(Klass klass) {
this.klass = klass;
}
public Klass getKlass() {
return klass;
}
}
What am I missing?

Deserializing transient fields with XStream 1.4.2

I've faced with a requirement to deserialize fields that possibly can be transient using XStream 1.4.2. Despite of that, such fields may be annotated with both #XStreamAlias and #XStreamAsAttribute. Yes, I know, it sounds weird, and this is an indicator of bad design, but this is what I currently have. Since XStream offers a way to specify custom converter, I tried to extend com.thoughtworks.xstream.converters.reflection.ReflectionConverter in order to override the default way of omitting all transient fields trying to make XStream allow to deserialize them. However, I've fully stuck having two ideas to implement such a converter, but none of them works. So here is what I tried:
The 1st way doesn't work:
public final class TransientSimpleConverter extends ReflectionConverter {
private final Class<?> type;
private TransientSimpleConverter(Class<?> type, Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
this.type = type;
}
public static TransientSimpleConverter transientSimpleConverter(Class<?> type, XStream xStream) {
return new TransientSimpleConverter(type, xStream.getMapper(), xStream.getReflectionProvider());
}
#Override
protected boolean shouldUnmarshalTransientFields() {
return true;
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
}
The 2nd way doesn't work either:
public final class TransientComplexConverter extends ReflectionConverter {
private final Class<?> type;
private TransientComplexConverter(Class<?> type, Mapper mapper, ReflectionProvider provider) {
super(mapper, provider);
this.type = type;
}
public static TransientComplexConverter transientComplexConverter(Class<?> type, Mapper mapper, Iterable<String> fieldNames) {
return new TransientComplexConverter(type, mapper, TransientHackReflectionProvider.transientHackReflectionProvider(type, fieldNames));
}
#Override
public boolean canConvert(Class type) {
return this.type == type;
}
private static final class TransientHackReflectionProvider extends PureJavaReflectionProvider {
private final Class<?> type;
private final Collection<Field> allowedFields;
private final Collection<String> allowedAliases;
private TransientHackReflectionProvider(Class<?> type, Collection<Field> allowedFields, Collection<String> allowedAliases) {
this.type = type;
this.allowedFields = allowedFields;
this.allowedAliases = allowedAliases;
}
public static TransientHackReflectionProvider transientHackReflectionProvider(final Class<?> type, Iterable<String> fieldNames) {
final Collection<Field> allowedFields = from(fieldNames).transform(new Function<String, Field>() {
#Override
public Field apply(String name) {
return field(type, name);
}
}).toList();
final Collection<String> allowedAliases = transform(allowedFields, new Function<Field, String>() {
#Override
public String apply(Field f) {
return f.getName();
}
});
return new TransientHackReflectionProvider(type, allowedFields, allowedAliases);
}
#Override
protected boolean fieldModifiersSupported(Field field) {
return allowedFields.contains(field) ? true : super.fieldModifiersSupported(field);
}
#Override
public boolean fieldDefinedInClass(String fieldName, Class type) {
return type == this.type && allowedAliases.contains(fieldName) ? true : super.fieldDefinedInClass(fieldName, type);
}
private static final Field field(Class<?> type, String name) {
try {
final Field field = type.getDeclaredField(name);
checkArgument(isTransient(field.getModifiers()), name + " is not transient");
checkArgument(field.getAnnotation(XStreamAsAttribute.class) != null, name + " must be annotated with XStreamAsAttribute");
checkArgument(field.getAnnotation(XStreamAlias.class) != null, name + " must be annotated with XStreamAlias");
return field;
} catch (final SecurityException ex) {
throw new RuntimeException(ex);
} catch (final NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
}
}
Any suggestions or ideas for a workaround? Thanks in advance.
I know this post is old, but maybe someone is still interested. My solution:
XStream xstream = new XStream(new MyPureJavaReflectionProvider());
class MyPureJavaReflectionProvider extends PureJavaReflectionProvider {
public MyPureJavaReflectionProvider() {
this(new FieldDictionary(new ImmutableFieldKeySorter()));
}
public MyPureJavaReflectionProvider(FieldDictionary fieldDictionary) {
super(fieldDictionary);
}
protected boolean fieldModifiersSupported(Field field) {
int modifiers = field.getModifiers();
return !Modifier.isStatic(modifiers);
}
public boolean fieldDefinedInClass(String fieldName, Class type) {
Field field = fieldDictionary.fieldOrNull(type, fieldName, null);
return field != null && fieldModifiersSupported(field);
}
}

Categories

Resources