Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/437.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript TypeScript:如何在类型之间来回转换对象_Javascript_Node.js_Typescript_Express_Casting - Fatal编程技术网

Javascript TypeScript:如何在类型之间来回转换对象

Javascript TypeScript:如何在类型之间来回转换对象,javascript,node.js,typescript,express,casting,Javascript,Node.js,Typescript,Express,Casting,假设我有两种类型 type A = { fi_name: string; l_name: string; f_name: string; } type B = { firstName: string; lastName: string; fullName: string; } 有没有一种方法可以轻松地在这些类型之间来回转换对象 额外的互联网点如果答案还处理嵌套类型和/或可以使用expressjs中间件实现。我不确定是否有任何互联网点值得为任意嵌套属性解决此问题。为了

假设我有两种类型

type A = {
  fi_name: string;
  l_name: string;
  f_name: string;
}

type B = {
  firstName: string;
  lastName: string;
  fullName: string;
} 
有没有一种方法可以轻松地在这些类型之间来回转换对象


额外的互联网点如果答案还处理嵌套类型和/或可以使用expressjs中间件实现。

我不确定是否有任何互联网点值得为任意嵌套属性解决此问题。为了以编程方式实现这一点,您需要函数接受从一种类型到另一种类型的“映射”模式。例如,给定您的类型
A
B
,您可以执行以下操作:

const aToB = makeMapper<A, B>()({
  f_name: "fullName",
  fi_name: "firstName",
  l_name: "lastName"
} as const);
这里我写了
lostName
而不是
lastName
,所以我希望上面的类型有一个错误,告诉我我在某种程度上得到了
lastName
属性

对于对象属性,您需要映射模式不仅接受重命名的键,还接受对象类型的映射器。例如:

interface AccountA {
  user: A
}
interface AccountB {
  user: B
}
const accountAToAccountB = makeMapper<AccountA, AccountB>()({
  user: ["user", aToB]
} as const);
对吧?


您可以在TypeScript中完成这项工作,但是类型非常难看而且复杂,实现需要大量类型断言。下面是一个可能的方法:

class Mapper<T extends object, M extends ObjectMapping<T>> {
  constructor(public mapping: M) { }
  map(obj: T): Mapped<T, M> {
    return Object.fromEntries(Object.entries(obj).map(([k, v]) => {
      if (!(k in this.mapping)) return [k, v];
      const m = this.mapping[k as keyof T];
      if (Array.isArray(m)) return [m[0], m[1] ? m[1].map(v) : v];
      return [m, v];
    })) as any;
  }
  reverseMap(obj: Mapped<T, M>): T {
    const revMapping: Record<string, any> = Object.fromEntries(Object.entries(this.mapping).map(([k, m]) => {
      if (Array.isArray(m)) return [m[0], [k, m[1]]];
      return [m, k];
    }));
    return Object.fromEntries(Object.entries(obj).map(([k, v]) => {
      if (!(k in revMapping)) return [k, v];
      const m = revMapping[k];
      if (Array.isArray(m)) return [m[0], m[1] ? m[1].reverseMap(v) : v];
      return [m, v];
    })) as any;
  }
}

type ObjectMapping<T extends object> = {
  [K in keyof T]: T[K] extends object ? readonly [PropertyKey, Mapper<T[K], any>?] : PropertyKey
}

type Mapped<T extends object, M extends ObjectMapping<T>> = {
  [K in keyof T as GetKey<M[K]>]: GetVal<M[K], T[K]>
} extends infer O ? { [K in keyof O]: O[K] } : never;
type GetKey<P> = P extends PropertyKey ? P : P extends readonly [PropertyKey, any?] ? P[0] : never;
type GetVal<P, V> = P extends PropertyKey ? V :
  P extends readonly [PropertyKey, Mapper<any, any>] ? V extends object ? Mapped<V, P[1]['mapping']> : V : V;

type Invalid<Err> = {
  errMsg: Err
}

function makeMapper<T extends object, U extends object>() {
  return <M extends ObjectMapping<T>>(mapping: (Mapped<T, M> extends U ? M : M & Invalid<
    { [K in keyof U as K extends keyof Mapped<T, M> ? Mapped<T, M>[K] extends U[K] ? never : K : K]:
      K extends keyof Mapped<T, M> ? Mapped<T, M>[K] extends U[K] ? never :
      ["wrong property type", U[K], "vs", Mapped<T, M>[K]] : "missing or misspelled property key" }
  >)) => new Mapper<T, M>(mapping as M)
}
类映射器{
构造函数(公共映射:M){}
映射(对象:T):映射{
返回Object.fromEntries(Object.entries(obj.map)([k,v])=>{
if(!(此映射中的k))返回[k,v];
常数m=这个映射[k作为T的键];
if(Array.isArray(m))返回[m[0],m[1]?m[1].map(v):v];
返回[m,v];
}))如有的话;
}
反向映射(对象:映射):T{
const revMapping:Record=Object.fromEntries(Object.entries(this.mapping).map([k,m])=>{
if(Array.isArray(m))返回[m[0],[k,m[1]];
返回[m,k];
}));
返回Object.fromEntries(Object.entries(obj.map)([k,v])=>{
如果(!(revMapping中的k))返回[k,v];
常数m=revMapping[k];
if(Array.isArray(m))返回[m[0],m[1]?m[1]。reverseMap(v):v];
返回[m,v];
}))如有的话;
}
}
类型ObjectMapping={
[K in keyof T]:T[K]扩展对象?只读[PropertyKey,Mapper?]:PropertyKey
}
类型映射={
[K in keyof T as GetKey]:GetVal
}你猜怎么着?{[K in keyof]:O[K]}:永远不会;
键入GetKey

=P扩展属性key?P:P扩展只读[属性键,任何?]?P[0]:从不; 类型GetVal=P扩展属性key?五: P扩展了readonly[PropertyKey,Mapper。该实现还递归地遍历

映射
模式,并将映射应用于输入对象的条目

让编译器在面对如此复杂的键入时生成有用的错误消息并不简单,因为TypeScript中目前没有“throw/invalid”类型(请参阅),所以我不得不使用一种变通方法

可能存在各种各样的边缘情况,当对象类型的属性是原语和其他对象类型的结合时,或者当属性名称冲突时,这样的东西在您考虑在任何类型的生产环境中使用之前都需要大量的测试,无论是在类型系统还是在运行时。>

所以你可以考虑你的实际用例的范围和你是否真的想要一些通用的ReMePrAppor,或者如果一个更硬编码的方法会更适合。对于一个像你的代码一样的类型< A/<代码>和<代码> b>代码>,即使没有边缘情况,上面的实现也可能是过度的。对于你的实际用例?你可以说


你没有想到一个简单的方法来创建两个映射函数吗?无论如何,你需要一个映射来描述像
fl\u name
-
firstName
这样的成对函数。我试图避免创建两个函数,即A2B()和B2A()。我希望有一个模块或某种内置方式能够简化事情。太棒了!正如预期的那样工作,从我的炉底感谢您!但我认为错误处理没有像您预期的那样工作;正如您在操场错误日志中看到的,
[…]类型中缺少属性“errMsg”[…]
它正在按预期工作,只是很难看。您必须预测“errMsg丢失并且在类型{errMsg:{someProp:{someProp:“拼写错误或缺少prop”}中是必需的”实际上意味着“您的映射拼写错误或缺少属性“someProp”。TS中没有对自定义错误消息的官方支持,因此这是一个解决方法。请参阅。再次感谢。
const a: AccountA = {
  user: {
    fi_name: "Harry",
    l_name: "Potter",
    f_name: "Harry Potter"
  }
}

const b: AccountB = accountAToAccountB.map(a);

console.log(b);
/* {
  "user": {
    "firstName": "Harry",
    "lastName": "Potter",
    "fullName": "Harry Potter"
  }
}  */

const aAgain: AccountA = accountAToAccountB.reverseMap(b);
console.log(aAgain);
/* {
  "user": {
    "fi_name": "Harry",
    "l_name": "Potter",
    "f_name": "Harry Potter"
  }
} */
class Mapper<T extends object, M extends ObjectMapping<T>> {
  constructor(public mapping: M) { }
  map(obj: T): Mapped<T, M> {
    return Object.fromEntries(Object.entries(obj).map(([k, v]) => {
      if (!(k in this.mapping)) return [k, v];
      const m = this.mapping[k as keyof T];
      if (Array.isArray(m)) return [m[0], m[1] ? m[1].map(v) : v];
      return [m, v];
    })) as any;
  }
  reverseMap(obj: Mapped<T, M>): T {
    const revMapping: Record<string, any> = Object.fromEntries(Object.entries(this.mapping).map(([k, m]) => {
      if (Array.isArray(m)) return [m[0], [k, m[1]]];
      return [m, k];
    }));
    return Object.fromEntries(Object.entries(obj).map(([k, v]) => {
      if (!(k in revMapping)) return [k, v];
      const m = revMapping[k];
      if (Array.isArray(m)) return [m[0], m[1] ? m[1].reverseMap(v) : v];
      return [m, v];
    })) as any;
  }
}

type ObjectMapping<T extends object> = {
  [K in keyof T]: T[K] extends object ? readonly [PropertyKey, Mapper<T[K], any>?] : PropertyKey
}

type Mapped<T extends object, M extends ObjectMapping<T>> = {
  [K in keyof T as GetKey<M[K]>]: GetVal<M[K], T[K]>
} extends infer O ? { [K in keyof O]: O[K] } : never;
type GetKey<P> = P extends PropertyKey ? P : P extends readonly [PropertyKey, any?] ? P[0] : never;
type GetVal<P, V> = P extends PropertyKey ? V :
  P extends readonly [PropertyKey, Mapper<any, any>] ? V extends object ? Mapped<V, P[1]['mapping']> : V : V;

type Invalid<Err> = {
  errMsg: Err
}

function makeMapper<T extends object, U extends object>() {
  return <M extends ObjectMapping<T>>(mapping: (Mapped<T, M> extends U ? M : M & Invalid<
    { [K in keyof U as K extends keyof Mapped<T, M> ? Mapped<T, M>[K] extends U[K] ? never : K : K]:
      K extends keyof Mapped<T, M> ? Mapped<T, M>[K] extends U[K] ? never :
      ["wrong property type", U[K], "vs", Mapped<T, M>[K]] : "missing or misspelled property key" }
  >)) => new Mapper<T, M>(mapping as M)
}