10

I have the following initial object

const initialValues = { name: 'Jon', email: 'jon@me.com' }

I would like to create an identical object except where all the values are boolean and default to false. Like so

const expected = { name: false, email: false }

I created the following function which does what I want

const expected = cloneWithDefaults<typeof initialValues>(initialValues)

function cloneWithDefaults<T>(values: T) {
  type U = { [K in keyof T]: boolean }
  const keys = Object.keys(values) as Array<keyof T>
  const partial: Partial<U> = keys.reduce<Partial<U>>((acc, cur) => {
    acc[cur] = false
    return acc
  }, {})
  const final = partial as U
  return final
}

I also created a second version which does not use reduce

function createCloneWithDefaultsV2<T extends { [key: string]: unknown }>(values: T) {
  type U = { [K in keyof T]: boolean }
  const keys = Object.keys(values) as Array<keyof U>
  const partial: Partial<U> = {}
  for (let k in keys) {
    partial[k] = false
  }
  const final = partial as U
  return final
}

I wonder if there is a better / more succinct way of doing this. In particular I would like to get rid of the two uses of as if possible.

In the v2 I wonder if there is any preference for unknown as opposed to any.

david_adler
  • 9,690
  • 6
  • 57
  • 97
  • 1
    What's your problem with `unknown`? You don't use the value in your function and there seems to be no requirement to limit the type of your values in any way. So if you want to accept any type an this place, any and unknown should be ok here. (afaik there's no difference here because you do not access the value) – Christoph Lütjen May 11 '19 at 13:51
  • I could use `any` as opposed to `unknown` just curious if there is a preference in this scenario. – david_adler May 11 '19 at 13:57
  • 1
    Unkown vs. any: https://stackoverflow.com/questions/51439843/unknown-vs-any – Christoph Lütjen May 11 '19 at 14:01

4 Answers4

8

Here's how I would write it:

    function cloneWithDefaults<T>(values: T) {
        return <{[key in keyof T]: boolean}> Object.entries(values).reduce((p, [k, v]) => Object.assign(p, { [k]: false }), {});
    }
    const initialValues = { name: 'Jon', email: 'jon@me.com' };
    const expected = cloneWithDefaults(initialValues);

Due to the reduce() call, I don't see an option without a cast. (Either from partial or from any.) Note that Object.entries() requires downlevelIteration ts compiler option to be true (https://www.typescriptlang.org/docs/handbook/compiler-options.html).

Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
2

Here is another alternative that builds on Christoph's answer:

const cloneWithDefaultValues = <T>(input: T) => Object
    .keys(input)
    .reduce(
        (clone, key) => ({ [key]: false, ...clone }),
        {} as Record<keyof T, boolean>
    );
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
0

You could also write a class with default values, so that:

class YourObject {
    name: string | boolean = false;
    email: string | boolean = false;

    constructor(
        name: string | boolean = false,
        email: string | boolean = false
    ) {
        this.name = name;
        this.email = email;
    }
}

// now, you can create an object with no parameters:

let obj = new YourObject();
console.log(obj);
// output:
//      Object { name: false, email: false }

// then assign whatever strings to its fields:
obj.name = "Jon";
obj.email = "jon@mail.com";

// or create a new one with known values:
let jon = new YourObject("Jon", "jon@me.com");
console.log(jon);
// output:
//      Object { name: "Jon", email: "jon@me.com" }

GalaBoo
  • 9
  • 3
0

This might be an alternative which uses only one as

function cloneWithDefaults<T>(values: T) {
     const exp: {[key in keyof T]: boolean} = Object.entries(values).reduce((p, [k, v]) => Object.assign(p, { [k]: false }), {} as {[key in keyof T]: boolean});
        return  exp;
    }
    const initialValues = { name: 'Jon', email: 'jon@me.com' };
    const expected = cloneWithDefaults(initialValues);
    console.log(">>>>>>", expected)
Mukesh Maurya
  • 340
  • 4
  • 16