6

I'm trying to create an io-ts interface of the following:

export interface myInterface {
  [key:string]?: string | undefined | null
}

I want to turn this into the io-ts equivalent. The end goal is to combine it with another existing io-ts interface:

export const MyOtherInterfaceV = t.interface({
  requiredProp1: ValidString// custom type, checks string is populated
  requiredProp2: ValidString
  // All other fields marked as required
})

export type MyOtherInterface = t.TypeOf<typeof MyOtherInterfaceV>;

The idea is I need a type to represent a payload which will have some fields we require and must be valid, and some that we don't know about and can be optional. We want to combine these for use later on in processing, eventually being stored in dynamodb.

jbailie1991
  • 1,305
  • 2
  • 21
  • 42

2 Answers2

9

I think the answer you're looking for is record:

const myInterfaceCodec = t.record(t.string, t.union([t.string, t.undefined, t.null]));
export type MyInterface = t.TypeOf<typeof myInterfaceCodec>;

=> type MyInterface = { [x: string]: string | null | undefined; }

Your use case:

const myInterfaceV = t.record(t.string, t.union([t.string, t.undefined, t.null]));
export type MyInterface = t.TypeOf<typeof myInterfaceV>;

const myOtherInterfaceV = t.intersection([
    t.type({
        requiredProp1: t.string,
        requiredProp2: t.string
    }),
    myInterfaceV
]);
export type MyOtherInterface = t.TypeOf<typeof myOtherInterfaceV>;

const a: MyOtherInterface = {
    requiredProp1: "string",
    requiredProp2: "string2"
};

const b: MyOtherInterface = {
    requiredProp1: "string",
    requiredProp2: "string2",
    optionalProp1: "hello",
    optionalProp2: "world"
};
vmatyi
  • 1,273
  • 10
  • 21
  • This does not work if say a `number` or `boolean` is used as values as well as `string` - TypeScript is happy but io-ts is not. Running `myOtherInterfaceV.decode` on `a` and `b` will show that. I have not found a solution – tar Dec 02 '22 at 14:19
  • No, it is not indeed. But the question was about strings. If you want to use number or boolean, you have to add those to the Record as well. But that's not io-ts specific, it's how typescript works. – vmatyi Jan 09 '23 at 15:17
2

Probably the most close to myInterface in io-ts is t.UnknownRecord

export const MyOtherInterfaceV = t.interface({
  requiredProp1: t.string,
  requiredProp2: t.string
})

const MyOtherInterface = t.intersection([ t.UnknownRecord, MyOtherInterfaceV ]);
Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55