#typechecking input Objects

13 messages · Page 1 of 1 (latest)

spare needle
#

How does one do proper type validation in TypeScript? I currently have this but having to constantly cast is quite annoying.

const toNewEntry = (obj: unknown): EntryWithoutId => {
  const isObject = (v: unknown) => v && typeof v === "object"

  const isEntry = (v: object) => "description" in v && "date" in v && "specialist" in v && "type" in v
  const isHospitalEntry = (v: EntryWithoutId): boolean => v.type === "Hospital" &&
    "discharge" in v &&
    "date" in v.discharge &&
    "criteria" in v.discharge
  const isHealthCheckEntry = (v: EntryWithoutId): boolean => v.type === "HealthCheck" &&
    "healthCheckrating" in v
  const isOccupationalHealthcareEntry = (v: EntryWithoutId): boolean => v.type === "OccupationalHealthcare" &&
    "emloyerName" in v
...
final osprey
#

@spare needle You can write these sort of type-guards by hand, but it's much easier to use a library that generatees it lke zod.

#

e.g. here's a simplified example of doing validation that doesn't have any type-casts or custom type guards:

type Person = { name: string, age: number };

const parsePerson = (data: unknown): Person | undefined {
  if(
      typeof data === "object" && data
      && "name" in data && typeof data.name === "string"
      && "age" in data && typeof data.age === "number"
  ) {
    return { 
      name: data.name,
      age: data.age
    }
  }
  return undefined;
}
#

(This does build a new object at the end: when you do something like typeof data.name === "string", that narrows the type of data.name but does not narrow the type of data itself)

spare needle
#

the difficult part is that I have objects which extend each other but are part of the same union type, which leaves me clueless at the manual approach, let me illustrate

#
interface baseFoo {
  bar: number;
  baz: string
}

interface BarFoo extends baseFoo {
  buz: {
    start: Date,
    end?: Date,
  }
}

interface BazFoo extends baseFoo {
  hello: string;
}

type Foos = BarFoo | BazFoo
#

my actual code:

#
  if (isObject(obj) && isEntry(obj)) {
    const newEntry: EntryWithoutId = {
      ...obj,
      description: parseDescription(obj.description),
      date: parseDate(obj.description),
      specialist: parseSpecialist(obj.specialist),
    };

    switch (true) {
      case isHospitalEntry(newEntry):
        return {
          ...newEntry,
          discharge: {
            date: parseDate(newEntry.discharge.date),
            criteria: parseCriteria(newEntry.discharge.criteria),
          }
        }
      case isHealthCheckEntry(newEntry):
      case isOccupationalHealthcareEntry(newEntry):
    }

  if (isObject(obj) && isEntry(obj)) {
    const newEntry: EntryWithoutId = {
      ...obj,
      description: parseDescription(obj.description),
      date: parseDate(obj.description),
      specialist: parseSpecialist(obj.specialist),
    };

    switch (true) {
      case isHospitalEntry(newEntry):
        return {
          ...newEntry,
          discharge: { // doesn't work
            date: parseDate(newEntry.discharge.date),
            criteria: parseCriteria(newEntry.discharge.criteria),
          }
        }
      case isHealthCheckEntry(newEntry):
      case isOccupationalHealthcareEntry(newEntry):
    }

#

overriding the object fields before parsing works in assignment but not in the switch ...

final osprey
#

You'd probably make a method for handling the base parts and then individual methods for the variants.

#
declare const parseBaseFoo: (data: unknown): baseFoo | undefined;

declare const parseBazFoo: (data: baseFoo) => BazFoo | undefined;
declare const parseBarFoo: (data: baseFoo) => BarFoo | undefined;


const parseFoos = (data: unknown): Foos | undefined => {
    const baseFoo = parseBaseFoo(data);
    if (!baseFoo) return undefined;
    return parseBarFoo(baseFoo) || parseBazFoo(baseFoo);
};

orchid reefBOT
#
retsam19#0

Preview:```ts
interface baseFoo {
bar: number
baz: string
}

interface BarFoo extends baseFoo {
buz: {
start: Date
end?: Date
}
}

interface BazFoo extends baseFoo {
hello: string
}

type Foos = BarFoo | BazFoo

const parseBaseFoo
...```

spare needle