DynamicMapper / DynamicMapper

Object to object mapping in JavaScript
https://dynamic-mapper.gitbook.io
MIT License
24 stars 5 forks source link

Enhance Auto-mapper to Support Type Casting for Matching Field Names #1

Open Harsh-trivs opened 2 months ago

Harsh-trivs commented 2 months ago

Currently, automapper enforces strict type checks on keys in source and destination objects. This can limit the flexibility of the mapping process, especially when field names match but their types differ. To address this, we propose removing type checks on keys and introducing a functionality that automatically type casts the source object's fields to match the destination type if the field names match.

Proposed Solution Remove Type Checks: Eliminate the enforcement of type checks on keys during the mapping process. Introduce Type Casting: Implement a feature that automatically type casts the source object's fields to match the destination type based on the field names. Benefits Increased Flexibility: Allows for more dynamic and flexible mapping between objects with matching field names but differing types. Ease of Use: Reduces the need for manual type conversions, streamlining the mapping process.

jsantha commented 2 months ago

We don't have an information about a destination property type during runtime. Everything need to be enforced during compilation time.

Given this example:

interface Source {
    value: string;
}

interface Destination {
   value: number;
}

We really don't know that Destination.value is of type number, interface Destination is just hint to TS compiler, no information to runtime is provided. The only way I can imagine would be decorating class properties with destination type, but that would work for class types only.

Harsh-trivs commented 2 months ago

We don't need to focus on types for implementation. We should simply map the source to the destination if the names match, assuming the key will be unique. Users can manage types themselves. While stricter typing and prompts for type errors would be preferred by some, it would limit the flexibility of the automapper. We can allow users to configure it according to their requirements and keep default typing enabled.

jsantha commented 2 months ago

I'm struggling to find a good use case for this feature. Automapping selling point is, that if property type matches, then no explicit declaration is needed.

interface Source {
    value: string;

}

interface Destination {
   value: string;
}

// so you can do this and all source properties would be mappped to destination
cfg.createAutomap(SourceToDestionatio);

but if you have some properties where type mismatches:

interface Source {
    value: string;
    another: string;
}

interface Destination {
  value: string;
  another: number;
}

// but this just needs to be converted to number otherwise it would broke contract
cfg.createAutomap(SourceToDestionation, {
   another: o => o.mapFrom(src => parseInt(src, 10))
});

Why would anyone wanted to map string to number property to just convert it manually later outside of mapper? I'm afraid I just cannot see your point, feel free to give me a hint or your scenario so I can understand better.

Harsh-trivs commented 2 months ago

Configuring for a nested interface where there is a type mismatch can be quite cumbersome.

For instance, consider a use case involving a database with numerous entries, where we want the automapper to map client models to backend models. This would necessitate changes on either the client or backend side, potentially causing significant backward compatibility issues. Since JSON objects are essentially key-value pairs with string keys, manual mapping avoids these problems.

jsantha commented 2 months ago

If you have nested interfaces, then you should use mapFromUsing and handle it more formally.

interface ServerRoot {
   name: string;
   child: ServerChild;
}

interface ServerChild {
   foo: string;
   bar: number;
}

interface ClientRoot {
  name: string;
  child: ClientChild;
}

interface ClientChild {
   foo: string;
   bar: string;
}

cfg.createAutomap(ServerChildToClientChild, {
   bar: o => o.mapFrom(src => src.bar.toString())
});

cfg.createAutomap(ServerRootToClientRoot, {
   child: o => o.mapFromUsing(src => src.child, ServerChildToClientChild), 
});

Why not to use it like that? Type would break on certain nesting level only and you can convert it accordingly.