Map object types using custom converter I'm trying to design a mapper that will convert one object type to another shown in the diagram below:



The object has structure as follows (it's not a JSON):

{
"type": "DownloadAppComponent",
"name": "Download App",
"contentId": "download-app",
"properties": {
    "iosUrl": "http://apple.com",
    "androidUrl": "http: //google.com",
    "promoText": "Download our app",
    "hidden": false
}


My first solution was to have mapper for each type but this required a lot of code duplication to map common attributes (i.e. name, type, contentId).

public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    downloadAppComponent.setType(cmsDocument.getType()); // &lt;-- this will be duplicated in each mapper
    downloadAppComponent.setName(cmsDocument.getName()); // &lt;-- this will be duplicated in each mapper
    downloadAppComponent.setContentId(cmsDocument.getText(CONTENT_ID_PATH)); // &lt;-- this will be duplicated in each mapper
    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}


I've been trying to refactor that code and I come up with generic BaseDocumentMapper:

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);
    document.setType(cmsDocument.getType());
    document.setName(cmsDocument.getName());
    document.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    return document;
}


The documentsMapperFactory returns specific mapper that will map only type related attributes and return that object instance.

However, there are still more levels in inheritance and I just have one type value which I can resolve to concrete mapper. So I'll have to repeat Component's specific fields mapping in each component mapper.

I was thinking that since I know the hierarchy I could create some mappers that will map the documents from Up to Bottom, i.e. create first DownloadAppComponent and then extend it with Component and then with BaseDocument specific properties. However, I have not found any good solution except using abstract classes and inheritance in mappers as well.

Could someone advise me if this is good approach or if there are any issues or other better solutions for my case?
The first thing that surprise me in your code is that your map method are non-static. You would need an instance of your component to create a different instance mapping a document.

I think the method to get Components from a document should be static:

public static DownloadAppComponent of(CmsDocument cmsDocument) {
    return new DownloadAppComponent().map();
}


Now, your map methods can initialize only the attributes specific of the Class and rely on the superclass to initialize the more generics attributes:

// Map method of DownloadAppComponent class
protected DownloadAppComponent map(CmsDocument cmsDocument) {
    super.map(cmsDocument);
    setIosURL(cmsDocument.getText(IOS_URL_PATH));
    setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));
    return this;
}

// Map method of Component class
protected Component map(CmsDocument cmsDocument) {
    super.map(cmsDocument);
    setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    return this;
}

// Map method of BaseDocument class
protected BaseDocument map(CmsDocument cmsDocument) {
    setType(cmsDocument.getType());
    setName(cmsDocument.getName());
    setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    return this;
}


EDIT:

If you want the map functionality in mapper classes, your mapper classes could implement a parallel hierarchy to the Component classes and still use inheritance to only initialize the attribute specific to each mapper:

public class DownloadAppComponentMapper extends ComponentMapper {

    public DownloadAppComponent of(CmsDocument cmsDocument) {
        return map(new DownloadAppComponent(), cmsDocument);
    }

    protected DownloadAppComponent map(DownloadAppComponent downloadAppComponent, CmsDocument cmsDocument) {
        super.map(downloadAppComponent, cmsDocument);
        downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
        downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
        downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
        downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));
        return downloadAppComponent;
    }
}

public class ComponentMapper extends BaseDocumentMapper {

    public Component of(CmsDocument cmsDocument) {
        return map(new Component(), cmsDocument);
    }
    protected ComponentMapper map(Component component, CmsDocument cmsDocument) {
        super.map(component, cmsDocument);
        component.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));            
        return component;
    }
}

public class BaseDocumentMapper {

    public BaseDocument of(CmsDocument cmsDocument) {
        return map(new BaseDocument(), cmsDocument);
    }
    protected BaseDocument map(BaseDocument baseDocument, CmsDocument cmsDocument) {
        baseDocument.setType(cmsDocument.getType());
        baseDocument.setName(cmsDocument.getName());
        baseDocument.setContentId(cmsDocument.getText(CONTENT_ID_PATH));            
        return baseDocument;
    }
}