flow-runtime
flow-runtime copied to clipboard
Is it possible to check if a JSON object validates against all properties of my class definition?
This is a:
- [ ] Bug Report
- [ ] Feature Request
- [X] Question
- [ ] Other
Which concerns:
- [X] flow-runtime
- [X] babel-plugin-flow-runtime
- [ ] flow-runtime-validators
- [ ] flow-runtime-mobx
- [ ] flow-config-parser
- [ ] The documentation website
What is the current behaviour?
I'm banging my head against the wall for a couple of hours now (yes, it hurts!). I'm trying to implement a validation for JSON objects. I have a class like:
import _ from "lodash";
class MySchema {
foo: string;
bar: string;
assign(payload: Object) {
_.assign(this, payload);
}
doSomething() { ... };
doSomethingElse() { ... };
}
In the MySchema#assign
above I'm now trying to validate a plain object payload = { foo: "foo", bar: "bar" }
against that class definition. I've tried a couple of different approaches without much success:
This code only checks that the payload is an instance of MySchema
, but doesn't check the properties foo
and bar
for validity.
const schemaValidator = (reify: Type< MySchema >);
schemaValidator.assert(_.assign(new MySchema(), payload));
There's little documentation but $Shape
looked like something useful. Unfortunately the following code just throws an "Can only $Shape<T> object types." exception:
const schemaValidator = (reify: Type< $Shape< MySchema > >);
schemaValidator.assert(payload);
The validation works as expected if I change MySchema
to a type
instead of a class
. But obviously that's not what I want as I'd like to provide additional functions (#doSomething
and #doSomethingElse
) in the class instance. There seems to be no way in Flow to have a class inherit from a Type.
Another way I found is to define MySchema
as an interface instead of a class, but this makes me repeat all properties in the actual class implementation like this:
interface MySchema {
foo: string;
bar: string;
}
class MyEntity implements MySchema {
foo: string; // <-- I have to duplicate all properties here
bar: string; // <-- yuck
doSomething() { ... };
doSomethingElse() { ... };
}
// But at least the validation seems to work now...
const schemaValidator = (reify: Type< MySchema >);
schemaValidator.assert(payload);
I had plenty of other iterations but can't get it to really work intuitively.
What is the expected behaviour?
I would hope to find a simple way of validating that my JSON object is indeed "compatible" with my class definition. Is that possible?
Which package versions are you using?
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
It's noteworthy that I'm disabling annotations in my .babelrc
to avoid any runtime performance penalties (not sure if there are any?). It still works perfectly fine if I define MySchema
above as a type
instead of a class. The validation code properly checks property types:
"plugins": [
[ "flow-runtime", { "assert": false, "annotate": false } ]
]
I've finally enabled annotations and I seemingly could get classes to work now as well:
"plugins": [
"transform-decorators-legacy",
[ "flow-runtime", { "assert": false, "annotate": true } ]
]
After enabling annotate
like this the following code actually worked:
const schemaValidator = (reify: Type< MySchema >);
schemaValidator.assert(payload);
But is this intended to work this way? It also doesn't seem to be a good solution as only a handful of classes should be validated against their definition. Most of the code doesn't take external input and I don't want to add any overhead. Guess I'm just confused how to use flow-runtime
properly.
@arabold
Since JSON is based upon plain objects it really only makes sense to assert against plain object types rather than classes. That flow-runtime
doesn't support $Shape
of a class is a bug, though you'll run into trouble using $Shape
of a class to validate JSON because it includes class methods:
/* @flow */
class Foo {
bar: number
baz(qux: number): void {}
}
const foo: $Shape<Foo> = {baz: 42}
6: const foo: $Shape<Foo> = {baz: 42}
^ property `baz` of object literal. Covariant property `baz` incompatible with contravariant use in
6: const foo: $Shape<Foo> = {baz: 42}
^ Foo
I'm not sure what you're trying to do with the MySchema
class and its methods above and beyond validating against a flow-runtime
type, but maybe you could do it this way?
type MySchemaFields = {
foo: string;
bar: string;
}
class MySchema {
fields: MySchemaFields;
// assertion happens here (unless you have flow-runtime assertions disabled)
constructor(fields: MySchemaFields) {
this.fields = fields;
}
doSomething() { ... };
doSomethingElse() { ... };
}