With dozer is it possible to map several fields to one field?
Asked Answered
P

4

6

We have some legacy data we are attempting to map... The legacy data has fields for month day year...

Is it possible to convert

MyObject.day
MyObject.year
MyObject.month

to

MyOtherObject.date

I could not find any documentation on this subject. Any would be appreciated.

Parallelize answered 15/12, 2015 at 19:30 Comment(0)
C
3

I know that this is an old post, but I could not find a satisfying answer, spent a lot of time and then discovered this (I think) easy method. You can use a ConfigurableCustomConver in combination with the 'this' reference in your mapping.xml.

For example:

public class Formatter implements ConfigurableCustomConverter
{
   private String format;
   private String[] fields;

   @Override
   public Object convert(Object existingDestinationFieldValue, Object           sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
      List valueList = new ArrayList();

      for (String field : fields){
        try {
           valueList.add(sourceClass.getMethod(field).invoke(sourceFieldValue));
        }
        catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
           throw new RuntimeException("Reflection error during mapping", e);
        }
     }
     return MessageFormat.format(format, valueList.toArray());
  }

  @Override
  public void setParameter(String parameter)
  {
     String[] parameters = parameter.split("\\|");
     format = parameters[0];
     fields = Arrays.copyOfRange( parameters, 1, parameters.length);
  }
}

and in your mapping.xml:

<mapping type="one-way">
    <class-a>test.model.TestFrom</class-a>
    <class-b>test.model.TestTo</class-b>

    <field custom-converter="nl.nxs.dozer.Formatter" custom-converter-param="{0}{1}|getFirstValue|getSecondValue">
        <a>this</a>
        <b>combinedValue</b>
    </field>
</mapping>

classes:

public class TestFrom
{
   private String firstValue;
   private String secondValue;

   public String getFirstValue()
   {
      return firstValue;
   }

   public void setFirstValue(String firstValue)
   {
      this.firstValue = firstValue;
   }

   public String getSecondValue()
   {
      return secondValue;
   }

   public void setSecondValue(String secondValue)
   {
      this.secondValue = secondValue;
   } 
}
public class TestTo
{
   private String combinedValue;  

   public String getCombinedValue(){
      return combinedValue;
   } 

   public void setCombinedValue(String combinedValue){
      this.combinedValue = combinedValue;
   }
}
Cartesian answered 11/10, 2018 at 11:38 Comment(3)
This is a really good example of what I needed when I asked this question.Parallelize
For completeness it would be very nice to see the TestFrom TestTo .. though we understand this library it is not as common as it once was.Parallelize
I added the missing classes for completeness.Cartesian
S
2

From my side I would recommend a little different solution if you need mapping two fields to single one and vice versa.

The Dozer has the possibility to user setters/getters as way to make mapping. On the class where you have to do it (where you have two fields and wants to map to single field) use the setters where you could provide single field from different object and on the setter/getter logic assign these two fields.

Example mapping:

<mapping type="bi-directional">
<class-a>ClassWithTwoFields</class-a>
<class-b>ClassWithSingleField</class-b>
<field>
  <a get-method="getHelperField" set-method="setHelperField" >helperField</a>
  <b>helperField</b>
</field>

Example getters/setters:

 public FieldType getHelperField() {
    if (!isNull(field1) && !isNull(field2)) {
        return field1 + field2;
    }
    return null;
}

public void setHelperField(FieldType singleField) {
    if (!isNull(singleField)) {
        this.field1 = singleField.part1();
        this.field2 = singleField.part2();
    }
}

It's quick way to deal with the problem.

Schnabel answered 7/3, 2017 at 12:12 Comment(2)
I ended up subtyping the object and doing this exact thing. Keeps the base object clean and pretty for the rest of the system and treats it as a converter classParallelize
This is one solution, unfortunately not if the classes you operate on are generated (from a xsd for example). In such a case see my solution below.Enfold
D
1

According to the Dozer documentation:

How do I map multiple fields to a single field?

Dozer doesn't currently support this. And because of the complexities around ? implementing it, this feature is not currently on the road map. A possible solution would be to wrap the multiple fields in a custom complex type and then define a custom converter for mapping between the complex type and the single field. This way, you could handle the custom logic required to map the three fields into the single one within the custom converter.

If you have control over your legacy data, you could create a type to wrap the individual date fields:

MyWrapperObject.java

public class MyWrapperObject {

    private int day;

    private int month;

    private int year;

}

MyObject.java

public class MyObject {

    private MyWrapperObject myWrapperObject;

}

and then use a custom converter to map the 3 fields into a date field:

MyOtherWrapperObject.java

public class MyOtherWrapperObject {

    private Date date;

}

DateCustomConverter .java

public class DateCustomConverter implements CustomConverter {

    @Override
    public Object convert(Object destination, Object source,
            @SuppressWarnings("rawtypes") Class destClass,
            @SuppressWarnings("rawtypes") Class sourceClass) {

        // Source object is null
        if (source == null) {
            return null;
        }

        if (source instanceof MyWrapperObject) {
            MyOtherWrapperObject dest = new MyOtherWrapperObject();
            MyWrapperObject src = (MyWrapperObject) source;

            Calendar c = Calendar.getInstance();
            // Months are 0 based in Java 
            c.set(src.getYear(), src.getMonth() - 1, src.getDay());
            dest.setDate(c.getTime());

            return dest;
        }
        return null;
    }

Then reference this converter in your XML mapping file:

dozer.xml

<mapping  map-null="false" >
    <class-a>com.xxx.MyObject</class-a>
    <class-b>com.xxx.MyOtherObject</class-b>

    ...

    <field custom-converter="com.xxx.DateCustomConverter">
        <a>myWrapperObject</a>
        <b>myOtherWrapperObject</b>
    </field>
</mapping>

If you don't have control over your legacy data then I believe you'd have to write a custom mapper to map MyObject to MyOtherObject.

Disengagement answered 7/1, 2016 at 9:20 Comment(1)
Dont like the answer, but its accurate lol Thank you. Would be nice to implement but I do see the complexity.Parallelize
E
1

This might not match the Question 100% but I came here trying to find this solution (as probably many others). What this does: Mapping a single field value to two fields in the target class, conditionally on which target is being adressed. Imagine a single "name" field which shall be mapped to a name and an name-abbreviation field in the target class. I'm using a Dozer "ConfigurableCustomConverter".

Mapping:

<field custom-converter="de.foo.MyConfCustomConverter" custom-converter-param="name">
  <a>person.name</a>
  <b>student.name</b>
</field>
<field custom-converter="de.foo.MyConfCustomConverter" custom-converter-param="abbreviation">
  <a>person.name</a>
  <b>student.shortName</b>
</field>

The matching custom converter:

public class MyConfCustomConverter implements ConfigurableCustomConverter {

private String param;

@SuppressWarnings("rawtypes")
@Override
public Object convert(Object destination, Object source, Class destClass, Class sourceClass) {
    if ("name".equals(this.param)) {
        return (String) source;
    }
    else if ("abbreviation".equals(this.param)) {
        return myAbbrevFunction((String) source);
    }
    return null;
}

@Override
public final void setParameter(String parameter) {
    this.param = parameter;
}

}

If you have additional complex types to map you can also implement "MapperAware" to use the Dozer Mapper function inside your CustomConverter:

public class MyConfCustomConverter implements ConfigurableCustomConverter, MapperAware{

private String param;
private Mapper mapper;

@SuppressWarnings("rawtypes")
@Override
public Object convert(Object destination, Object source, Class destClass, Class sourceClass) {
    if ("name".equals(this.param)) {
        return (String) source;
    }
    else if ("abbreviation".equals(this.param)) {
        if(isSourceClassAComplexType()){
            return mapper.map(source, my.target.package.ComplexType.class);
        }
        return myAbbrevFunction((String) source);
    }
    return null;
}

@Override
public final void setParameter(String parameter) {
    this.param = parameter;
}

}
Enfold answered 11/1, 2018 at 15:29 Comment(1)
An other solution is to write a converter and give "this" to the converter so you can then access the fields of the source class in the converter. At this point however you're working around dozer effectively and this should only be done as an absolute exception.Enfold

© 2022 - 2024 — McMap. All rights reserved.