0

How can I map an Array List from Source to Destination with the two classes below? Need the DTO to map to lookup array. Have been testing map function, still not working. Also, should we make the mapFromSourceAddressDto method to a separate Export function, or inside the Lookup class itself, would it make it easier?

There maybe a chance that the backend DTO API models can change, so trying to create a typesafe way to map subset columns, and be notified if DTO column name changes.

export class SourceOfAddressDto {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;
    createDate: date;
    userId: string;
}

export class SourceOfAddressLookup {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;       
}

export function mapFromSourceOfAddressDto(sourceOfAddressDto: SourceOfAddressDto){
    const sourceOfAddressLookup = new SourceOfAddressLookup();
    sourceOfAddressLookup .sourceOfAddressId = sourceOfAddressDto.sourceOfAddressId
    sourceOfAddressLookup .sourceOfAddressCode = sourceOfAddressDto.sourceOfAddressCode;
    sourceOfAddressLookup .sourceOfAddressDescription = sourceOfAddressDto.sourceOfAddressDescription
    sourceOfAddressLookup .sourceOfAddressLabel = sourceOfAddressDto.sourceOfAddressLabel;

    return sourceOfAddressLookup ;
}

Goal: Take an Array<SourceofAddressDto> ---> Array<SourceOfAddressLookup>

Attempted Solution:

looking for cleaner way,

public addressSourceList: Array<SourceOfAddressLookup>; 

if (addressSourceListDto.length > 0){
    for (let i = 0; i < addressSourceListDto.length; ++i ) {
      this.addressSourceList[i] = mapFromSourceOfAddressDto(addressSourceListDto[i])
    }
  }
7
  • I'm afraid I don't understand the question. Could you edit the code into a minimal reproducible example which clearly shows what you're trying to do and where it's not working? Good luck! Commented Feb 20, 2020 at 20:34
  • How are you calling the mapFromSourceOfAddressDto function? Commented Feb 20, 2020 at 20:46
  • see attempted solution above @jcalz Commented Feb 20, 2020 at 20:58
  • see attempted solution above @HereticMonkey it may help Commented Feb 20, 2020 at 20:59
  • Is there a particular reason you need classes and not interfaces? Classes should have their properties initialized, so createDate: date; userId: string; are errors. And date is not a known type; do you mean Date? Commented Feb 20, 2020 at 21:14

2 Answers 2

1

I think you may be making things more complicated than it needs to be.

A more effective way to ensure type safety in this scenario would be first to define two typescript interfaces.

One for your DTO data structure (which I presume comes from an api request). The other for your 'destination' object structure.

interface SourceOfAddressDto {
  sourceOfAddressId?: number;
  sourceOfAddressCode?: string;
  sourceOfAddressDescription?: string;
  sourceOfAddressLabel?: string;
  createDate: string;
  userId: string;
}

interface SourceOfAddress {
  sourceOfAddressId?: number;
  sourceOfAddressCode?: string;
  sourceOfAddressDescription?: string;
  sourceOfAddressLabel?: string;
}

You can define your map function separately with a return type specified

const mapItems = (item:SourceOfAddressDto):SourceOfAddress[] => {
  return {
    sourceOfAddressId: item.sourceOfAddressId;
    sourceOfAddressCode: item.sourceOfAddressCode;
    sourceOfAddressDescription: item.sourceOfAddressDescription;
    sourceOfAddressLabel: item.sourceOfAddressLabel;
  }
};

When you retrieve your async data you can then map it directly:

const data = await fetch("http://api") as SourceOfAddressDto[];
const mappedData = data.map(mapItems);
Sign up to request clarification or add additional context in comments.

3 Comments

this is lot nice, however, right now the mapItems returns array, one if I wanted to conduct a 1 on 1 case basis, have reusability in the function?
also, heard I cannot create new instances of interfaces, but classes I can
I don't think you need to create class instances as you are working with very basic object structures. In this case it is fine to use an array of object literals.
0

NEW ANSWER:

Okay, I'm switching this around: your DTO is an interface corresponding to plain-old-javascript-objects that you get from your API:

interface SourceOfAddressDto {
    sourceOfAddressId?: number | undefined;
    sourceOfAddressCode?: string | undefined;
    sourceOfAddressDescription?: string | undefined;
    sourceOfAddressLabel?: string | undefined;
    createDate: Date;
    userId: string;
}

And your Lookup is a bona fide class with methods that you need to use, like the shout() one I'll make up as an example:

class SourceOfAddressLookup {
    sourceOfAddressId?: number | undefined;
    sourceOfAddressCode?: string | undefined;
    sourceOfAddressDescription?: string | undefined;
    sourceOfAddressLabel?: string | undefined;
    shout() {
        console.log("HELLO MY ID IS " + this.sourceOfAddressId +
            " AND MY CODE IS \"" + this.sourceOfAddressCode + "\"" +
            " AND MY DESCRIPTION IS \"" + this.sourceOfAddressDescription + "\"" +
            " AND MY LABEL IS \"" + this.sourceOfAddressLabel + "\"!");
    }
}

Instead of pluck() from before, I'll define assignProps(), which takes a destination object, a source object, and a list of property keys to copy from the source to the destination. It's generic so the compiler should yell at you if for some reason the source's properties are not the right type for the destination:

function assignProps<T, K extends keyof T>(
    destination: T, 
    source: Pick<T, K>, 
    ...keys: K[]
): T {
    keys.forEach(k => destination[k] = source[k]);
    return destination;
}

So now, mapFromSourceOfAddressDto takes a SourceOfAddressDto and calls assignProps() on a newly constructed SourceOfAddressLookup instance:

const mapFromSourceOfAddressDto = (dto: SourceOfAddressDto) => assignProps(
    new SourceOfAddressLookup(),
    dto,
    "sourceOfAddressId",
    "sourceOfAddressCode",
    "sourceOfAddressDescription",
    "sourceOfAddressLabel"
)

This compiles with no error, so the types should work. Then you can do array mapping pretty easily like this:

class Foo {
    public addressSourceList: Array<SourceOfAddressLookup>;
    constructor(addressSourceListDto: Array<SourceOfAddressDto>) {
        this.addressSourceList = addressSourceListDto.map(mapFromSourceOfAddressDto);
    }
}

And let's test it by constructing something with an array of SourceOfAddressDto objects:

const foo = new Foo([{
    createDate: new Date(),
    userId: "abc",
    sourceOfAddressId: 0,
    sourceOfAddressDescription: "d",
    sourceOfAddressCode: "c",
    sourceOfAddressLabel: "l"
}, {
    createDate: new Date(),
    userId: "def",
    sourceOfAddressId: 1,
    sourceOfAddressDescription: "D",
    sourceOfAddressCode: "C",
    sourceOfAddressLabel: "L"
}]);

That should be mapped by Foo's constructor to an array of SourceOfAddressLookup instances, so let's test it by calling the shout() method for each element:

foo.addressSourceList.forEach(x => x.shout())
// HELLO MY ID IS 0 AND MY CODE IS "c" AND MY DESCRIPTION IS "d" AND MY LABEL IS "l"! 
// HELLO MY ID IS 1 AND MY CODE IS "C" AND MY DESCRIPTION IS "D" AND MY LABEL IS "L"!

Okay, looks good. Good luck again!

Playground link to code


OLD ANSWER:

I assume that your DTO is a full-fledged class with methods

class SourceOfAddressDto {
    sourceOfAddressId: number | undefined;
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
    sourceOfAddressLabel: string | undefined;
    createDate: Date;
    userId: string;
    constructor(createDate: Date, userId: string) {
        this.createDate = createDate; this.userId = userId;
    }
}

but that the lookup type can be an interface which just has some of the same properties. If you want this type to have a name you can do it this way:

// if you must name this type, you can do this:
interface SourceOfAddressLookup extends Pick<SourceOfAddressDto,
    "sourceOfAddressCode" |
    "sourceOfAddressDescription" |
    "sourceOfAddressId" |
    "sourceOfAddressLabel"
    > { }

Anyway in general I'd use a function like pluck() to take an existing object and make a new one by copying a list of properties:

function pluck<T, K extends keyof T>(t: T, ...k: K[]) {
    return k.reduce((a, k) => (a[k] = t[k], a), {} as Pick<T, K>)
}

And then your mapFromSourceOfAddressDto function can use it like this:

function mapFromSourceOfAddressDto(obj: SourceOfAddressDto): SourceOfAddressLookup {
    return pluck(
        obj,
        "sourceOfAddressId",
        "sourceOfAddressCode",
        "sourceOfAddressDescription",
        "sourceOfAddressLabel"
    );
}

And we can make sure it works:

const dto = new SourceOfAddressDto(new Date(), "abc");
dto.sourceOfAddressCode = "cod";
dto.sourceOfAddressDescription = "descrip";
dto.sourceOfAddressLabel = "lab";
dto.sourceOfAddressId = 1;

const lookup = mapFromSourceOfAddressDto(dto);

console.log(JSON.stringify(lookup));

/* {
"sourceOfAddressId":1,
"sourceOfAddressCode":"cod",
"sourceOfAddressDescription":"descrip",
"sourceOfAddressLabel":"lab"
}
*/

Looks good to me. Hope this helps give you some direction; good luck!

Playground link to code

8 Comments

cool, this is helpful, how do I conduct this use your mapFromSourceAddresss Dto for an array into another array? you did for single item, thanks
sourceOfAddressDtoArray.map(mapFromSourceOfAddressDto)?
the DTO comes from API and has no methods, my lookup value has methods it concanetates, capitalizes words, etc
feel free to write in answer, was just trying that,
oh, so I did it backwards... so you need to hydrate a class with props from an object and not the reverse
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.