Typescript: is there a way to guarantee that all properties of an interface are addressed

Created on 31 Mar 2017  路  7Comments  路  Source: microsoft/TypeScript

say i have an interface

    interface Data { one: number; another: string; }

i have a serialization method

    serialize(data: Data): string {
        return 'one: ' + data.one + ', another: ' + data.another;
    }

now i am doing refactoring and want to add a new property to the Data interface, so that

    interface Data { one: number, another: string, yetAnother: boolean }

now by requirement the new property yetAnother must be serialized too, yet the compiler won't point out to that because i didn't ask for it, so there is a chance of a bug to slip in

now my question: is there a way to ask the TypeScript compiler if i addressed all properties of an interface?

crossposted to StackOverflow: http://stackoverflow.com/questions/43140675/is-there-a-way-to-guarantee-that-all-properties-of-an-interface-are-addressed

UPDATE

thank to @RyanCavanaugh 's idea, here is what i ended up with

below is a full working example

try it live at playground:

type Around<T, R> = { [P in keyof T]: (result: R, value: T[P], name: P) => R; }
function around<T, R>(data: T, result: R, around: Around<T, R>): R {
    for (const name in data) {
        result = around[name](result, data[name], name);
    }
    return result;
}

function identity<T>(value: T): T { return value; }
function addOver<T>(toString: (value: T) => string) {
    return function add(values: string[], value: T, name: string): string[] {
         values.push(name + ': ' + toString(value));
         return values;
    };
}

interface Data { one: number, another: string, yetAnother: boolean }

function serialize(data: Data): string {
   return around<Data, string[]>(data, [], {
       one: addOver(String),
       another: addOver(identity),
       yetAnother: addOver(String)
   }).join(', ');
}

const serialized = serialize({ one: 1, another: 'hey', yetAnother: true });
alert(serialized) // one: 1, another: hey, yetAnother: true
Question

All 7 comments

Would be nice see that also with switch enums:

const enum Foo {
   A, B, C
}
switch (x) {
  case A: return 1;
  case B: return 1;
  // C missed
}

@aleksey-bykov here's a fairly odd solution you may or may not like

interface Data { one: number; another: string; theNewOne: Date }

function assertNever(x: never) {
    throw new Error(`Oops! Forgot about ${x}`);
}

function serialize(data: Data) {
    var keys = Object.keys(data) as Array<keyof Data>;
    const parts: string[] = [];
    for (const k of keys) {
        switch (k) {
            case 'one':
                parts.push('uno: ', data.one.toString());
                break;
            case 'another':
                parts.push('another: ', data.another);
                break;
            case 'mispeld': // Error
                break;
            default:
                assertNever(k); //  Error, can't convert theNewOne to never               
        }
    }
    return parts.join('');
} 

i like it, thanks

inspired by @RyanCavanaugh, i ended up with some extremely elegant little function:

export type Around<T extends object, R> = { [P in keyof T]: (result: R, value: T[P], name: P) => R; }
export function around<T extends object, R>(data: T, result: R, around: Around<T, R>): R {
    for (const name in data) {
        result = around[name](result, data[name], name);
    }
    return result;
}

problem solved

@aleksey-bykov How to use this function?

check my latest update to the initial post in this topic, there is an example there

Was this page helpful?
0 / 5 - 0 ratings