Generisk mappare med Typescript


Användningsfallet här är till exempel att mappa ett fetch-svar med ett JSON-resultat till en mer komplex klass med hjälp av generiska typer.

Till exempel mapper<T,U>(U json) där T är den förväntade returtypen. Och U är valfritt json-klassen.

U kan vara en array t.ex. vilket resulterar i att T[] eller Array<T> mappas ut.

Till exempel givet en User:

interface CustomAdressImplementation {
  street: string;
  streetNumber: string;
  city: string;
  zip: string;
}

interface User {
  id: string;
  birthDate: Date;
  name: string;
  address: CustomAdressImplementation;
}

exempel på json UserJson:

[
  {
    "id": 1,
    "birthdate": "2020-02-20T10:00:19.145Z"
    "name": "Leanne Graham"
    "address": {
      "street": "Kulas Light",
      "streetNo": "2b"
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
  },
  [... more users] 
]

Ett sätt att anpassa mappningen på detta är helt enkelt genom att skapa en mappningsmetod:

interface GeoJson {
  lat: string;
  lng: string;
}

interface AddressJson {
  street: string;
  streetNo: string;
  suite: string;
  city: string;
  zipcode: string;
  geo: GeoJson;
}

interface UserJson {
  id: number;
  birthdate: string;
  name: string;
  address: AddressJson;
}

export function mapAddress(json: AddressJson): CustomAdressImplementation {
  return {
    street: json.street,
    streetNumber: json.streetNo,
    city: json.city,
    zip: json.zipcode,
  };
}

export function mapUser(json: UserJson): User {
  return {
    id: json.id.toString(),
    birthDate: new Date(json.birthdate),
    name: json.name,
    address: mapAddress(json.address),
  };
}

export function mapUserList(json: UserJson[]): User[] {
  if (json) return json.map(mapUser);
  return [];
}

Finns det ett mer elegant sätt att skapa ett regelset för att mappa detta med hjälp av generics? Eller är manuell anpassad mappning vägen att gå i TypeScript när man hanterar komplex mappningslogik.

I mitt exempel verkar datum-, strängkonvertering och mappning till adressklassen kunna skrivas mer elegant och undvika den rutinmässiga anpassade mappningsmetoden.

Att använda class-transformer-biblioteket är inte en accepterad lösning, letar efter en ren Typescript-implementation som använder generics.

Svar #1

AutoMapper TypeScript är ett verktyg som tillhandahåller objekt-till-objekt-typmappning enligt konvention och följer så nära som möjligt det ursprungliga .NET AutoMapper.

Exemplet här är ganska likt problemets beskrivning och ger också en lösning.

Svar #2

Du kan skapa en mapList-funktion som härleder den skickade mappningen:

// Some UserJson type
type UserJson = {
  [key: string]: any;
};

// User type
type User = {
  id: string;
  birthDate: Date;
  name: string;
  orderLines: any;
};

// All mappers should receive a json of type T and return a type U
interface Mapper<T, U> {
  (json: T): U;
}

// We declare the mapper of user, should receive a UserJson (T) and returns an User (U)
type mapUser = Mapper<UserJson, User>;

function mapUser(json: UserJson): User {
  return {
    id: json.id.toString(),
    birthDate: new Date(json.birthdate),
    name: json.name,
    orderLines: json.address
  };
}

// mapList receives a Mapper<T, U> and returns a function with type (json: T[]) => U[]
function mapList<T, U>(mapper: Mapper<T, U>): (json: T[]) => U[] {
  return (json: T[]): U[] => {
    if (json) return json.map(mapper);
    return [];
  };
}

// As mapList received a Mapper<UserJson, User>, it will return (json: UserJson[]) => User[]
const mapUserList = mapList(mapUser);

Demo

Relaterade artiklar