Search code examples
javadozer

Is there a way to make Dozer map fields without getter or setters? (Or, what mapper can do this?)


Here's an article on Dozer: https://www.baeldung.com/dozer. It's a mapper that uses reflection to map same-name fields from one object to another (of a completely unrelated class).

I was wondering if this works flexibly with private fields, getters, and setters. That is,

  • Will private String a map to another object's private String a without either having any getters or setters?

  • What if only one side has a getter or setter (and the private field is named something different to make sure it's not directly accessing private fields)?

  • What if one has a getter and the other has a setter for totally mismatching private fields? (But the getter and setter names match.)

I wrote a test program to run in https://www.jdoodle.com/online-java-compiler:

import org.dozer.DozerBeanMapper;

public class Main {

    public static class MySource {

        // a -> a
        private String a;

        // getB() -> b
        private String hidden_b;
        public String getB() { return hidden_b; }

        // c -> setC(c)
        private String c;

        // getD() -> setD(d)
        private String hidden_d;

        // proper getters and setters on both sides
        private String proper;
        public String getProper() { return proper; }
        // public void setProper(String proper_) { proper = proper_; }

        public MySource() {
            a = "A Room with a View";
            hidden_b = "The Bridge of San Luis Rey";
            c = "Civilwarland in Bad Decline";
            hidden_d = "Darkness at Noon";
            proper = "This should copy, at minimum.";
        }

        public void print() {
            System.out.println("Source");
            System.out.println("================================");
            System.out.println("a        = " + a);
            System.out.println("hidden_b = " + hidden_b);
            System.out.println("c        = " + c);
            System.out.println("hidden_d = " + hidden_d);
            System.out.println("--------------------------------");
            System.out.println("proper   = " + proper);
            System.out.println("");
        }
    }

    public static class MyTarget {

        private String a;
        private String b;
        private String hidden_c;
        private String hidden_e;

        public void setC(String param) { hidden_c = param; }
        public void setD(String param) { hidden_e = param; }

        private String proper;
        // public String getProper() { return proper; }
        public void setProper(String proper_) { proper = proper_; }

        public MyTarget() {}

        public void print() {
            System.out.println("Target");
            System.out.println("================================");
            System.out.println("a        = " + a);
            System.out.println("b        = " + b);
            System.out.println("hidden_c = " + hidden_c);
            System.out.println("hidden_e = " + hidden_e);
            System.out.println("--------------------------------");
            System.out.println("proper   = " + proper);
            System.out.println("");
        }
    }

    public static void main(String args[]) {
        MySource s = new MySource();
        s.print();

        System.out.println("Now dozing...");
        System.out.println("");

        MyTarget t = new DozerBeanMapper().map(s, MyTarget.class);
        t.print();
    }
}

Note that to run the above code you must add a maven dependency:

Group ID:    net.sf.dozer
Artifact ID: dozer
Version:     5.5.1

And also you must try executing a few times because of random timeouts depending on whether the dependency loads fast enough.

Anyway, my output was:

Source
================================
a        = A Room with a View
hidden_b = The Bridge of San Luis Rey
c        = Civilwarland in Bad Decline
hidden_d = Darkness at Noon
--------------------------------
proper   = This should copy, at minimum.

Now dozing...

Target
================================
a        = null
b        = null
hidden_c = null
hidden_e = null
--------------------------------
proper   = This should copy, at minimum.

So, it appears Dozer only works through a getter on the source and a setter on the target, which is disappointing. Or, I'm not using it correctly!

Is there a way to make Dozer more flexible? Or, another mapper library that can achieve this?


Solution

  • Okay, here are my findings. Hopefully this helps someone.

    Dozer 5.5.1 was supposed to be able to do this via "class-level is-accessible." However, there was a bug. It was fixed for future releases, e.g. Dozer 6.1+. (The package moved to a new group, org.github.dozermapper.) The steps were a little complicated though, and eventually I gave up to try ModelMapper, which was much nicer. So here's my code.

    Include this package:

    Group ID:    org.modelmapper
    Artifact ID: modelmapper
    Version:     2.3.2
    

    Here's how to use it:

    import org.modelmapper.ModelMapper;
    import org.modelmapper.config.Configuration;
    
    public class Main {
    
        public static class MySource {
    
            // a -> a
            private String a;
    
            // getB() -> b
            private String hidden_b;
            public String getB() { return hidden_b; }
    
            // c -> setC(c)
            private String c;
    
            // getD() -> setD(d)
            private String hidden_d;
    
            // proper getters and setters on both sides
            private String proper;
            public String getProper() { return proper; }
            // public void setProper(String proper_) { proper = proper_; }
    
            public MySource() {
                a = "A Room with a View";
                hidden_b = "The Bridge of San Luis Rey";
                c = "Civilwarland in Bad Decline";
                hidden_d = "Darkness at Noon";
                proper = "This should copy, at minimum.";
            }
    
            public void print() {
                System.out.println("Source");
                System.out.println("================================");
                System.out.println("a        = " + a);
                System.out.println("hidden_b = " + hidden_b);
                System.out.println("c        = " + c);
                System.out.println("hidden_d = " + hidden_d);
                System.out.println("--------------------------------");
                System.out.println("proper   = " + proper);
                System.out.println("");
            }
        }
    
        public static class MyTarget {
    
            private String a;
            private String b;
            private String hidden_c;
            private String hidden_e;
    
            public void setC(String param) { hidden_c = param; }
            public void setD(String param) { hidden_e = param; }
    
            private String proper;
            // public String getProper() { return proper; }
            public void setProper(String proper_) { proper = proper_; }
    
            public MyTarget() {}
    
            public void print() {
                System.out.println("Target");
                System.out.println("================================");
                System.out.println("a        = " + a);
                System.out.println("b        = " + b);
                System.out.println("hidden_c = " + hidden_c);
                System.out.println("hidden_e = " + hidden_e);
                System.out.println("--------------------------------");
                System.out.println("proper   = " + proper);
                System.out.println("");
            }
        }
    
        public static void main(String args[]) {
    
            ModelMapper modelMapper = new ModelMapper();
            modelMapper.getConfiguration()
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);
    
            MySource s = new MySource();
            s.print();
    
            System.out.println("Now dozing...");
            System.out.println("");
    
            MyTarget t = modelMapper.map(s, MyTarget.class);
            t.print();
        }
    }
    

    Here's my output:

    Source
    ================================
    a        = A Room with a View
    hidden_b = The Bridge of San Luis Rey
    c        = Civilwarland in Bad Decline
    hidden_d = Darkness at Noon
    --------------------------------
    proper   = This should copy, at minimum.
    
    Now dozing...
    
    Target
    ================================
    a        = A Room with a View
    b        = The Bridge of San Luis Rey
    hidden_c = Civilwarland in Bad Decline
    hidden_e = null
    --------------------------------
    proper   = This should copy, at minimum.
    

    The fourth case didn't copy over but I don't really care about that case. I think it can easily achieved with a different ModelMapper configuration though. Maybe try LOOSE copying. Or worst case, manually bind the getter and setter methods in the config.