ThomasAribart / json-schema-to-ts

Infer TS types from JSON schemas 📝
MIT License
1.47k stars 31 forks source link

Can deserialization patterns use the property name? #94

Closed halfcadence closed 1 year ago

halfcadence commented 2 years ago

I'm trying to use a custom deserialization to type check CSS in my JSON objects.

I'm using a CSS type fromcsstype:

import type * as CSS from "csstype"; 
type CustomStyle = CSS.Properties<string | number>;

And have the CSS as a property called "customStyle" in my schemas:

const schemaWithCustomStyle =  {
  type: "object",
  properties: {
    customStyle: {
      type: "object",
    },
  },
  additionalProperties: false,
} as const;

The type I would like to get from this is something like

type TypeWithCustomStyle = {
    customStyle?: CustomStyle;
}

However since there's not enough information in the type to match a pattern:

type TypeWithCustomStyle = FromSchema<
  typeof schemaWithCustomStyle,
  {
    deserialize: [
      {
        pattern: {
          type: "object";
        };
        output: CustomStyle;
      }
    ];
  }
>;

matches the outer schema:

type TypeWithCustomStyle = CSS.Properties<string | number, string & {}>

Is there a way to pattern match based on the name of the property, e.g.

type TypeWithCustomStyle = FromSchema<
  typeof schemaWithCustomStyle,
  {
    deserialize: [
      {
        pattern: {
          propertyName: "customStyle";
        };
        output: CustomStyle;
      }
    ];
  }
>;

type TypeWithCustomStyle = {
    customStyle?: CustomStyle;
}

I took a look at the deserialize.ts file:

type RecurseOnDeserializationPatterns<
  S extends JSONSchema7,
  P extends DeserializationPattern[],
  R = M.Any
> = {
  stop: R;
  continue: RecurseOnDeserializationPatterns<
    S,
    L.Tail<P>,
    S extends L.Head<P>["pattern"]
      ? M.$Intersect<M.Any<true, L.Head<P>["output"]>, R>
      : R
  >;
}[P extends [any, ...any[]] ? "continue" : "stop"];

but don't understand how exactly it's working.

ThomasAribart commented 1 year ago

Hi @halfcadence !

You are right, it's not possible right now to use the property name in deserialization patterns. I'll look into that, it could be an interesting feature.

Meanwhile, there's a very simple trick you can use (that may even be cleaner than using the property name): JSON schemas are extensible, so you can use a custom internalId prop:

const schemaWithCustomStyle =  {
  type: "object",
  properties: {
    customStyle: {
      internalId: "customStyle",
      type: "object",
    },
  },
  additionalProperties: false,
} as const;

type TypeWithCustomStyle = FromSchema<
  typeof schemaWithCustomStyle,
  {
    deserialize: [
      {
        pattern: {
          internalId: "customStyle";
        };
        output: CustomStyle;
      }
    ];
  }
>;
halfcadence commented 1 year ago

Thanks for the comment, that makes sense and I think would solve this problem.

(As a different issue, I realized that the schema I'm working with is recursive and so isn't supported for that reason)