mojotech / json-type-validation

TypeScript JSON type validation
MIT License
155 stars 15 forks source link

Recursion not working #38

Closed pablonardone closed 5 years ago

pablonardone commented 5 years ago

Hello, I was testing some recursive JSON, but while the library does not give any error, it does not detect the incorrect fields either. Here is my example:

const text = `
{
  "val1":1,
  "val2": [
    {
      "WRONG_val1":2,
      "val2":null
    },
    {
      "val1":3,
      "val2":null
    }]
}`;

interface Elem {
  val1: number;
  val2: Elem[];
};

var elemDecoder: Decoder<Elem> = object({
  val1: number(),
  val2: array(elemDecoder)
});

function Decode() {
  try {
    let elem1: Elem = JSON.parse(text);
    console.log("elem1", elem1);
    let elem2: Elem = elemDecoder.runWithException(elem1);
    console.log("elem2", elem2);
  }
  catch (error) {
    console.error(error);
  }
}

As you can see the field name "WRONG_val1" is not defined in the interface, but the decoder does not detect any error and returns a JSON object. Here is the link to the running code at stackblits. You have to open the console for seeing the results.

So I was wondering if recursion is supported? Thank you very much. Regards, Pablo

mulias commented 5 years ago

Hi Pablo!

Thanks so much for the clear explanation and linking to example code.

The issue is that in your recursive type you're using elemDecoder before it is properly declared. If we use a const instead of var then typescript will give an error about this issue:

const elemDecoder: Decoder<Elem> = object({
  val1: number(),
  val2: array(elemDecoder) // Error: Block-scoped variable 'elemDecoder' used before its declaration.
});

The solution is to use the lazy decoder, which is specifically for delaying computation so that we can represent recursive types

const elemDecoder: Decoder<Elem> = object({
  val1: number(),
  val2: array(lazy(() => elemDecoder))
});

with that change we get an error in the console Screenshot_2019-04-28_17-18-56

mulias commented 5 years ago

I just realized why you were not getting any runtime errors. When the array decoder is called without arguments it will validate any json array, without checking contents. So at runtime you were executing the decoder array(undefined), since elemDecoder did not yet have a value. As a result the decoder checked that the input was an array, which it was. This is an unfortunate bug, but I'm not sure there's a clean fix to avoid it from the library side. I think the best thing to do is use const.

pablonardone commented 5 years ago

I didn't know about the the lazy decoder, but now is working perfectly. Thank you very much for the support, and continue with the great work. Regards, Pablo