首页 > 解决方案 > 递归重新映射对象中的键时键入返回值

问题描述

我有一个接收任何对象的映射器,如果其中有一个名为“字段”的键,它会被它的内容替换。例如,

const obj = { x: 'hey', fields: { y: 'you' } }
const mapped = contentfulMapper(obj);

// mapped = { x: 'hey', y: 'you' }

或者

const givenObj = {
  a: "sdf",
  w: { 
    a2: 343,
    a3: true,
    fields: {
      a4: "ll",
      q: {
        fields: {
          a5: [2, 4, "lk"]
        }, 
      },
    },
  }, 
};
const mapped = contentfulMapper(givenObj);

/* mapped = {
  a: "sdf",
  w: { 
    a2: 343,
    a3: true,
    a4: "ll",
    q: {
      a5: [2, 4, "lk"]
    }, 
  }, 
}; */

虽然返回 aRecord<string, unknown>不是很好,所以我的问题是如何正确输入?

type TContentfulModel = {
  fields?: undefined | Record<string, TContentfulModel>;
};


const contentfulMapper = (
  obj: Record<string, TContentfulModel | unknown>
) => {
  let out: Record<string, unknown> = {};

  Object.keys(obj).forEach((key) => {
    const field = obj[key] as TContentfulModel;
    if (key === 'fields') {
      const mappedField = contentfulMapper(field);
      out = { ...out, ...mappedField };
      
    } else {
      out[key] =
        typeof obj[key] === 'object' && !Array.isArray(obj[key])
          ? contentfulMapper(field)
          : obj[key];
    }
  });

  return out;
};

标签: javascripttypescripttypes

解决方案


这是一个有效的解决方案。请通过它,如果没有得到任何东西,请告诉我。那么,我将添加任何需要的解释。

type TContentfulModelExtended = {
  fields: TContentfulModelExtended | TContentfulModel
  [key: string]: any
}

type TContentfulModel = {
  [key: string]: any
}

// if `T` has `fields`, we omit that key, keep rest,
// and take it's intersection with the flatten nested fields
type Flat<T extends TContentfulModelExtended | TContentfulModel> = T extends TContentfulModelExtended
  ? Omit<T, 'fields'> & Flat<T['fields']>
  : T

type MyObjType = {
  a: string
  fields: {
    b: number
    c: boolean
    fields: {
      d: number[],
      fields: {
        e: Promise<number>
      }
    }
  }
}

type Flattened = Flat<MyObjType>

const obj: Flattened = {
  a: 'abc',
  b: 3,
  c: false,
  d: [3],
  e: Promise.resolve(3)
}


const contentfulMapper = <O extends TContentfulModelExtended | TContentfulModel>(obj: O) => {
  let out: Flat<O> = {} as any;

  Object.keys(obj).forEach((key) => {
    const field = obj[key];
    if (key === 'fields') {
    // @ts-ignore
      const mappedField = contentfulMapper(field);
      out = { ...out, ...mappedField };
      
    } else {
      out[key] =
        typeof obj[key] === 'object' && !Array.isArray(obj[key])
          ? contentfulMapper(field)
          : obj[key];
    }
  });

  // @ts-ignore
  return out;
};

const givenObj = {
  a: 'sdf',
  fields: {
    a2: 343,
    a3: true,
    fields: {
      a4: 'll',
      fields: {
        a5: [2, 4, 'lk']
      }
    }
  }
}

const objSpreaded = contentfulMapper(givenObj)
objSpreaded.a // all props from `a` to `e` works properly
objSpreaded.a2
objSpreaded.a3
objSpreaded.a4
objSpreaded.a5
objSpreaded.f // expected error

[操场]

请注意,我添加了编译器指令注释// @ts-ignore以消除无限深度错误。


推荐阅读