0

I'm following up on a previous question:

I have the following schema:

const schemas = {
  POST: {
    $schema: 'https://json-schema.org/draft/2019-09/schema#',
    $id: 'https://api.netbizup.com/v1/health/schema.json',
    type: 'object',
    properties: {
      body: {
        type: 'object',
        properties: {
          greeting: {
            type: 'string',
          },
        },
        additionalProperties: false,
      },
    },
    required: ['body'],
  } as const,
  PUT: {
    $schema: 'https://json-schema.org/draft/2019-09/schema#',
    $id: 'https://api.netbizup.com/v1/health/schema.json',
    type: 'object',
    properties: {
      body: {
        type: 'object',
        properties: {
          modified: {
            type: 'boolean',
          },
        },
        required: ['modified'],
        additionalProperties: false,
      },
    },
    required: ['body'],
  } as const,
};

I am using FromSchema from the json-schema-to-ts package to infer the type of each body attribute the const object above.

What I need to achieve is an intersection type of POST and PUT bodies. The answer provided in the previous question was perfect to create a TypeScript Union. The problem is of course that when I try to access members of body I get an error since not all attributes will always exist on each body.

I was even wondering if TypeScript allows us to verify that if we access one attribute from a "chosen" body object, then it won't allow us to access the other.

I created a Playground to illustrate my question.

ANNEX

Playground where the answer partially works: https://www.typescriptlang.org/play?target=7&module=6&pretty=true#code/JYWwDg9gTgLgBAbzgMShEBlAxgCwKYgCGcAvnAGZohwDkAVgM4QB2AtA7gYazBDwzQDcAWABQoSLERwAggAUAkgHFCMPAHdCATzloAHloCiANzzMYANQBMAdWAwcAKRsAVGQFcH0YAC88UUgoqWkJ1BlYAG0IQACMAE0IhMTEsFgZ4DnwiBjgAXkQxODg5AHkMFwAuAtEiooASTK4qmhwYGDAGCoB6LsYWdk4iADpoAHMuuKhCchguqwAGAEYATlZ55a7GogBiGgAaQtq64Djm1vbOnsIwYCHmPBgY33cwIdSQLuNFrvxCCIdNoNCEM+sx9ocijAtGA8M0IDE6HgsDBwTVamA0DDYMA8J1qrVajEIHEtFUEBCCXAoTC4QikSiDmjKXAMRAsTAcXjyUzmXBRlA8A9gMxRmSKbyqdDYbR0lBhaNURKiiRGRKVeLaoQ4nF7MAWH9dGz-BzcVVyH8GHhVZT1TzbQSBQBHdzAAWnOAAbRoRJJNAAutayIQcqlmOlrXIAKqVfFHLaEM5tDrdXpMNjxkZQcaTaazBYrNYbeO7a31E6Ji4p663e6PZ6vd6fb6-f44QFZYGgxW1anSmjwxHI7tFVnszlinlFH2k2PM3u0wcMjUjzHG8ez3kgYnAcg493cpU9qXNIkQCJ4Qhg0u8+0368OvDO114d1erc63cv-33zXa3X6iJDTHU0KAtK0NVvZVrydF03SqL1p2-Q4gxDNIYEZEgRFEMRezgBRmDATxsA7ABpPAtDyWglEMFwaDgAAfWhSnKOjGJoKNaIYpiZBcABhAAJVjaAAEUMAAZGjDCSbDRFw-DCJgYiuByfJuQASA9ABrci4GFPCCKIoEyK0P0yRZVdsRApBpyqdxmE05gIHUZhAkwsQ3Jkno4BsHBVBoHJ1DQEU4HUewcDgFwlKIOA8D0NRmB1YL5MMjsckIIlTAAfjELyFCpHBhU0-LgByEq4GIGJ3FGXSXJcKVsDlMB4HUfABTge50hfcyjVgCjCCwLBcRyFgarUKBzUGrpcIHelJRhHIBQYdx-lKlz-DQKAGCGHCpTgAAhYktCiwgAB5IqBGK4rMOIciQLTyKqWV5VM6RRzXKy4BsuA7IcpyXLIAGAD5KPOjt7q0CByAi46-S9N7LIEWHvUO-0sJyro4HOZMenSfrNIgUxxoiJy3nQLpnVxDk0i6ABWGnFisAB2eZ5gAFh+JyeD4GApjDchoBAVg7L1Nhey5ngXnPHgpWSUQvIgcKiQouIWBoeAVeKhgdphOBI2YEWXAgfCxstZERZOyNgdyQ4AApI0u+KbvK5gKMyuAbc0qpIwASjyYHjAgE44Cqe5Cd92LHZyG2PaqYVyH8PDfdyf3A7iX23bykO8EJ7W8DgMTgxgEpyDOq3Dj1g2jfMfxTap5gzod660pduA3ZtpPgZcYP2uz-xgYjpv3Y79244TgAldO4DH7vQ-8WWvMi1mhnmABqXPincBgcAbgeEubrQPQDOALCtz0hnPlw9mPv00bljHF6GRY19k3aK5YQ2XElvAzqvsTKIL9IxdS5XwAHKUQ9C4P0jc96elnlAKBbsebuDzmaMCZc0RILzrvJ2YC3aH27nITe2837MA-l-E6hg9BYAiO4OI39L750BoDX+gNZa4V4iwQmMAZARAiD-Ke0CnZjwvCrZgEQtAyCgFMLQJ0fqOWcqfQ+p8u7YJyB6UeARkCunDHAc+QwNFT0pgguAHDmBcJ4Xw4ROiPR6LHlfVA6BjonS0ZtGAgM-TAyqGPW+uEHEgAscdBgFtT6mPMbwi2+t34QE-mAc8wTAYemYO4WI-gb5sN2i4A6JJKLWUOlUPxASgRBKyUdIEJ1eyQzgPGBgTDBCkFlqGdILIIDpBKVUTJh0cmfTydIfkgoOQimaPxPAvCIAAEI6IAywo0+Al4IAOH8BYP4Jw5AtJgG0iKJSulfSQFMlIaEWSeA2R07JqlukkjMu+Hce4qiYNcj43aJT+KXjiOeKAZ0SnHVPjbMQale7mCqCUEA9gTryGUKoDQ2hDQGBMGYSwtgwrODcJ4HA3g-BQCvsjX0wMABk1Q1JqS+ic0pHZBG3TIG7ApvDAknU+WU4lXzPFVJ5vKLCalbTDwDicW+MzzlaGeQlN5lEzoCtef4OlpL8hyL+oDH5og1K+UFf4KoTyXlvJFWq8Vh0vliGHkgAUMB3BQH+g0g5iqxUBHyNOUV6qqURBpRKrg5SpSVOqUw2V5UGBaGYFgd2-yYB6sOI0s8eAhjE1GDbCpUN-VDGnN7LCJBvZAA

Sammy
  • 3,395
  • 7
  • 49
  • 95
  • It seems as if FromSchema cannot handle a union of the schemas. You can get around this by turning the union into a tuple, converting each schema in the tuple with FromSchema, then converting it back to a union. – kelsny Mar 08 '22 at 13:15

1 Answers1

1

It seems as if FromSchema cannot handle a union of the schemas. You can get around this by turning the union into a tuple, converting each schema in the tuple with FromSchema, then converting it back to a union.

For this we will first need the excellent holy grail of a post in How to transform union type to tuple type

I've renamed TuplifyUnion to UnionToTuple here.

Let us then write a type that converts a union of schemas we wish to transform.

We need to convert this union to a tuple, then convert each of the schemas separately, and finally turn the resulting tuple back into a union.

type FromAllSchemas<U> = ConvertAll<UnionToTuple<U>>[number];

This is done with UnionToTuple and [number].

Now let's write ConvertAll.

type ConvertAll<T, R extends ReadonlyArray<unknown> = []> =
    T extends [infer First, ...infer Rest]
        ? ConvertAll<Rest, [...R, FromSchema<First>]>
        : R;

First we infer the first element and the rest as a tuple.

We then convert the first element to a type with FromSchema, and put it in the results. Then we "call" ConvertAll again, this time with the rest of the elements and new result.

Finally, if we couldn't infer the first element that means T is empty, so we just return the result R.

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48
  • I'm going to try all this now and mark your answer; but irrespective of the results, thanks a bunch for being so patient and comprehensive. – Sammy Mar 08 '22 at 15:38
  • Thanks again. If you may please take a look at this playground: (I added the URL to the annex of the question since I cannot paste it here) you'll see that even though it works directly, I cannot pass the exact same type as a generic to a function -- it then gives the body type as unknown. – Sammy Mar 09 '22 at 07:54
  • Hi you can make it a link like [this](https://example.com) and use https://tsplay.dev to shorten links :) – kelsny Mar 09 '22 at 13:15
  • @Sammy Ok to answer your question, change the line where you call `bodyHandler`. It should only be `bodyHandler` because you convert the schema inside `BodyHandler` already! – kelsny Mar 09 '22 at 13:19
  • Yes, you are right. It's just that the problem prevails -- I'm talking now about the original problem that you resolved: https://tsplay.dev/W48R7W – Sammy Mar 09 '22 at 13:27
  • Oh if you're going to use it like that... You could make an assertion type guard or instead of a union of bodies merge them all... but that does not seem like the best. What is the use case here? – kelsny Mar 09 '22 at 13:29
  • Yeah, the use case is that the middleware might receive HTTP requests as PUT or POST (amongst others), each having its own body format. – Sammy Mar 09 '22 at 14:33
  • You might want to try type guards first if there aren't too many body types. – kelsny Mar 09 '22 at 22:42