2

Let's say I've got an object like this:

class MyObject {
    requiredString: string,
    nullableString: string | undefined
}

and I want to be able to deserialize JSON into this object while enforcing that it has the required string, but not enforcing that it has the nullable one.

So, all these tests should pass:

const validJson1 = '{"requiredString":"a needed value!","nullableString":"an unneeded value!"}'
const validJson2 = '{"requiredString":"whatever!"}'
const invalidJson = '{"nullableString":"I am the God Of Teeth!"}'

class MyObject {
    requiredString: string,
    nullableString: string | undefined
}

function myMagicValidator<T>( objectInstance:T ) {
    // Throws exception if objectInstance is not a valid <T>
}

test('works whatsoever', () => {
    let testObject = JSON.parse(validJson1)
    myMagicValidator<MyObject>(testObject)
})

test('respects optional values', () => {
    let testObject = JSON.parse(validJson2)
    myMagicValidator<MyObject>(testObject)
})

test('enforces required values', () => {
    let testObject = JSON.parse(invalidJson)
    expect(myMagicValidator<MyObject>(testObject)).toThrow()
})

// Ideally this could work too, but it's not strictly needed
const invalidJson2 = '{"requiredString":5,"nullableString":"another value!"}'
test('enforces type safety', () => {
    let testObject = JSON.parse(invalidJson2)
    expect(myMagicValidator<MyObject>(testObject)).toThrow()
})

But the trick is that I don't own the object, MyObject actually gets imported from another package and isn't modifiable in my code. The requirement is that if another field is added to MyObject that's required, myMagicValidator should automatically start checking for it when node_modules updates to include the new field.

StolenKitten
  • 337
  • 1
  • 10
  • The current top answer is correct. I suggest to use `io-ts` to perform such validations at runtime, especially if the shape to validate gets more complex than a single js check. See for instance my answer here https://stackoverflow.com/a/56773079/11401030. The great thing about io-ts is that, no matter how complex the validation is, downstream you'll end up with static type guarantees if it is successful – Giovanni Gonzaga Jun 27 '19 at 06:53

1 Answers1

2

TypeScript is inherently static. The types are stripped during compile-time and do not exist at runtime, so you can't check the object shape at runtime. For runtime checks, just use JS code.

function myMagicValidator<T>( objectInstance:T ) {
  if (!Reflect.has(objectInstance, 'requiredString')) {
    throw new Error('Missing requiredString');
  }
}

Note: depending on your need (and syntactic preferences), you can also use Object.prototype.hasOwnProperty or the in operator to check for property existence.

nem035
  • 34,790
  • 6
  • 87
  • 99
  • The trouble is, how do I get that list of required strings at runtime? – StolenKitten Jun 27 '19 at 16:43
  • You'd need to define a runtime type and validate against that yourself. There are many libs to use for this purpose, ine idea is something like proptypes in React, json-gate, etc – nem035 Jun 27 '19 at 16:45