1

I wan't to create a generic function for validating objects. I want it to accept an options object of the following signature:

export interface ValidationOptions<T, K extends keyof T = keyof T> {
  validators: Map<K, (value: T[K]) => string | null>    
}

I want to be able to map from K to an array of validator functions that would accept an argument of type T[K]. The issue i have is that T[K] will resolve to each possible value in T, and not the specific value for the given key.

The code below should hopefully clarify what i mean.

export interface ValidationOptions<T, K extends keyof T = keyof T> {
    validators: Map<K, (value: T[K]) => string | null>    
}

function fooValidator(value: string) {
    if (value === "foo") {
        return "Value can't be foo"
    }

    return null;
}

function isNotTrueValidator(value: boolean) {
    if (!value) {
        return "Value must be true"
    }

    return null;
}

interface ObjectToValidate {
    stringy: string;
    booly: boolean;
}


//(value: T[K]) => string | null will resolve to value: string | boolean here
//Can i make it resolve to the type for the given property instead? 
const options: ValidationOptions<ObjectToValidate> = {
    //This results in an error with strictFunctionTypes enabled
    validators: new Map([
        //How can i 
        ["stringy", fooValidator] 
    ])
}
Erik Johansson
  • 299
  • 1
  • 2
  • 6

1 Answers1

3

I would strongly suggest giving up on using a Map if the intent is just to hold values for string-based keys. That's what a plain old object is for. Importantly for TypeScript, there is a lot of support for object-based types, and you can easily represent the behavior you expect from validators via a mapped type, like this:

export interface ValidationOptions<T> {
    validators: { [K in keyof T]?: (value: T[K]) => string | null }
}

const options: ValidationOptions<ObjectToValidate> = {
    validators: {
        stringy: fooValidator
    }
}

If for some reason you need to keep using Map, the built-in TypeScript typings won't work, because they act more like a record type where each key is K and each value is V and the two types are independent. One can represent a new type called, say, ObjectMap whose typing is based on an underlying relationship between keys and values, but that's a lot of type munging to get where you're going.

Hope that helps; good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Thank you, this works perfectly! I started of with an object based approach but ran into some issues with with using keyof T as an index signature, but it seems like i need to read up on mappped types. Again, thank you very much! – Erik Johansson Apr 08 '19 at 15:47