nartc / mapper

🔥 An Object-Object AutoMapper for TypeScript 🔥
https://automapperts.netlify.app/
MIT License
961 stars 84 forks source link

fix(core): add falsey check in hasProperty; prevents error w/ namingConventions improperly mapped names #572

Closed jersmart closed 10 months ago

jersmart commented 10 months ago

There are a few issues I noticed when debugging the same error found in these issues:

https://github.com/nartc/mapper/issues/525 https://github.com/nartc/mapper/issues/551

1) When using naming conventions, the naming convention configuration override will be incorrect during InitialMapping, I think because it uses the namingConvention provided at the mapping level. This can result in some funky behavior when initial mapping happens, like separating my_thing into ["my","_Thing"]. 2) If it finds a similarly named field in the destination (in the example above, "my", it will try to treat that field as an object even when it is not an object, resulting in hasProperty throwing an error due to the obj parameter being undefined.

Example code that causes an issue:

import {
  CamelCaseNamingConvention,
  SnakeCaseNamingConvention,
  addProfile,
  createMap,
  createMapper,
  forMember,
  mapFrom,
  namingConventions,
} from "@automapper/core";
import { PojosMetadataMap, pojos } from "@automapper/pojos";

export interface ISubThingOne {
  name: string;
  description: string;
  imageUrls: string[];
}

export interface IThingOne {
  currency: string;
  unitAmount: number;
  productData: ISubThingOne;
  somethingElse: string;
}

export interface IThingTwo {
  currency_code: string;
  value: number;
  something_else: string;
}

PojosMetadataMap.create<ISubThingOne>("ISubThingOne", {
  name: String,
  description: String,
  imageUrls: [String],
});

PojosMetadataMap.create<IThingOne>("IThingOne", {
  currency: String,
  unitAmount: Number,
  productData: "ISubThingOne",
  somethingElse: String,
});

PojosMetadataMap.create<IThingTwo>("IThingTwo", {
  currency_code: String,
  value: Number,
  something_else: String,
});

const mapper = createMapper({
  strategyInitializer: pojos(),
  namingConventions: new CamelCaseNamingConvention(),
});

addProfile(
  mapper,
  (mapper) => {
    createMap<IThingOne, IThingTwo>(
      mapper,
      "IThingOne",
      "IThingTwo",
      forMember(
        (d) => d.currency_code,
        mapFrom((s) => s.currency)
      ),
      forMember(
        (d) => d.value,
        mapFrom((s) => s.unitAmount)
      )
    );
  },
  namingConventions({
    source: new CamelCaseNamingConvention(),
    destination: new SnakeCaseNamingConvention(),
  })
);

const thingOne: IThingOne = {
  currency: "USD",
  unitAmount: 100,
  productData: {} as ISubThingOne,
  somethingElse: "else",
};

const thingTwo = mapper.map<IThingOne, IThingTwo>(
  thingOne,
  "IThingOne",
  "IThingTwo"
);

console.log(thingTwo);

This PR fixes only issue (2) from the list above. There seems to be another issue that namingConventions overrides are loaded too late for initial mapping. However, once something is actually mapped, it works as expected, resulting in the following output:

{ something_else: 'else', currency_code: 'USD', value: 100 }

ISSUES CLOSED: #525, #551

sonarcloud[bot] commented 10 months ago

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

jersmart commented 10 months ago

Closing this PR as I should have made it from its own branch. Will reopen from the correct branch.