nartc / mapper

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

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

Open jersmart opened 1 year ago

jersmart commented 1 year 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 1 year 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

sonarcloud[bot] commented 10 months ago

Quality Gate Passed Quality Gate passed

Kudos, no new issues were introduced!

0 New issues
0 Security Hotspots
No data about Coverage
0.5% Duplication on New Code

See analysis details on SonarCloud