kickstartDS / storybook-addon-jsonschema

Displays associated JSON Schema documentation using a rich schema explorer interface, and adds a validating JSON Code Editor with semantic auto-complete to interact with your components, and copy configurations with ease.
https://www.kickstartDS.com/docs/integration/storybook/schema
MIT License
12 stars 1 forks source link

Schema "AnyOf" generated as "anything" #17

Open remigailard80 opened 2 years ago

remigailard80 commented 2 years ago

I generate a schema of this type,

export type ViewTypeReducerType =
  AdvertisementOverviewViewType.AsObject
  | ButtonTextViewType.AsObject 
  | FleaMarketOverviewViewType.AsObject
  | EventOverviewViewType.AsObject
  | GeneralBusinessOverviewViewType.AsObject
  | GeneralOverviewViewType.AsObject
  | GeneralProgressviewViewType.AsObject
  | GeneralWatchOverviewViewType.AsObject
  | ImageBannerViewType.AsObject
  | NoticeOverviewViewType.AsObject
  | PopupOverviewViewType.AsObject
  | QuestionViewType.AsObject
  | RegionCheckInViewType.AsObject

and generated stuff was

"ViewTypeReducerType": {
      "anyOf": [
        {
          "type": "object",
          "required": [
            "type"
          ],

.....

, the anyOf of objects.

and generated result on storybook was,

스크린샷 2022-03-22 오후 2 31 38

is there any method to resolve anyof schema correctly?

julrich commented 2 years ago

Hey @remigailard80, will have a look at that, too! I don't think we have anyOf in the addon yet. Do you use the Controls-addon, too? How do you handle the anyOf for that, out of curiosity? :thinking:

julrich commented 2 years ago

After some first testing, anyOf should generally work, we have one case here:

grafik https://www.kickstartds.com/storybook/?path=/story/base-section--inhaltsboxen

Could you post a slightly bigger excerpt of the anyOf you're using? The example above looks like this in our code:

    "content": {
      "type": "array",
      "items": {
        "anyOf": [
          {
            "$ref": "http://frontend.ruhmesmeile.com/content/organisms/quotes-slider.schema.json"
          },
          {
            "$ref": "http://frontend.ruhmesmeile.com/base/atoms/link-button.schema.json"
          },
          ...
        ]
      }
    },

And those $refs get inlined by json-schema-ref-parser before being put into the viewer.

remigailard80 commented 2 years ago

I think I found the problem, In the anyOf elements,

{
  "$ref": "#/definitions/QuestionViewType.AsObject"
},

was the problem. If I delete that element from the anyOf array, it parsed correctly as

anyOf [object, object, object , ... ]

remigailard80 commented 2 years ago

Hey @remigailard80, will have a look at that, too! I don't think we have anyOf in the addon yet. Do you use the Controls-addon, too? How do you handle the anyOf for that, out of curiosity? 🤔

using Controls-addon .

julrich commented 2 years ago

I think I found the problem, In the anyOf elements,

{
  "$ref": "#/definitions/QuestionViewType.AsObject"
},

was the problem. If I delete that element from the anyOf array, it parsed correctly as

anyOf [object, object, object , ... ]

That's really interesting. Because it hints at the problem, I think! Because I don't know how those inline $refs are handled actually. Have you tried just removing all $refs by running json-schema-ref-parser over it? :thinking: That should eliminate the problem entirely!

About controls-addon: How do you handle the anyOf there? I guess it's just a JSON field in the controls, right?

remigailard80 commented 2 years ago

I use json-schema-ref-parser and remove almost all of $ref but,

{
  "QuestionViewDataType": {
      "$ref": "#/definitions/QuestionViewType.Data.AsObject"
    },
    "QuestionViewType.Data.AsObject": {
      "type": "object",
      "properties": {
        "question": {
          "$ref": "#/definitions/QuestionViewType.Question.AsObject"
        }
      },
      "additionalProperties": false
    },
    "QuestionViewType.Question.AsObject": {
      "type": "object",
      "properties": {
        "id": {
          "type": "number"
        },
        "content": {
          "type": "string"
        },
        "kind": {
          "type": "number",
          "enum": [
            0,
            1,
            2,
            3
          ]
        },
        "negativeButtonText": {
          "type": "string"
        },
        "positiveButtonText": {
          "type": "string"
        },
        "androidTargetUri": {
          "type": "string"
        },
        "iosTargetUri": {
          "type": "string"
        },
        "questions": {
          "$ref": "#/definitions/QuestionViewType.Question.AsObject"
        }
      },
      "required": [
        "id",
        "content",
        "kind",
        "negativeButtonText",
        "positiveButtonText",
        "androidTargetUri",
        "iosTargetUri"
      ],
      "additionalProperties": false
    },
    "QuestionViewType.KindType": {
      "type": "number",
      "enum": [
        0,
        1,
        2,
        3
      ]
    }
  }
  }

because of this schema's circular ref, I added circular: "ignore" option.

this is my schema generator for json-schema-ref-parser

const $RefParser = require('@apidevtools/json-schema-ref-parser');
const schema = require('./schema.json');

// safely handles circular references
JSON.safeStringify = (obj, indent = 2) => {
    let cache = []; // circular references
    const retVal = JSON.stringify(
      obj,
        (key, value) => {
            return typeof value === "object" && value !== null
              ? cache.includes(value)
                ? value // Duplicate reference found, discard key
                : cache.push(value) && value // Store value in our collection
                    : value
        },
      indent
    );
    cache = null;
    return retVal;
};

$RefParser.dereference(schema, {
    continueOnError: true,
    resolve: {
        external: true
    },
    dereference: {
        circular: "ignore"
    }
}).then(res => console.log(JSON.safeStringify(res)))

that's the reason why there remains $ref, as i think.

About controls-addon: How do you handle the anyOf there? I guess it's just a JSON field in the controls, right?

right.

julrich commented 2 years ago

Could you keep those definitions in the file? (e.g. #/definitions/QuestionViewType.Data.AsObject) Or did you just cut them manually, to make the snippet shorter? :thinking:

Maybe it would just work, if you'd have those inline $refs only?

Alternatively you could replace those remaining $refs manually, in an additional processing pass. I know we're using https://github.com/epoberezkin/json-schema-traverse in some places to do similar stuff. You could traverse the whole schema, before putting it into the addon, and just watch for the $ref and replace it manually!

Edit: I think if it really is recursive, and needs to be displayed as such, it could get challenging!

remigailard80 commented 2 years ago

Uploaded snippet is part of my schema(too long), so i cut them manually, which caused problem.

Maybe it would just work, if you'd have those inline $refs only?

is this meaning it is enough to work if there's inline $refs only ? Because I'm not good at English, so I may have misunderstood that phrase.

If I understood correctly, that cause some issue on storybook like,

스크린샷 2022-03-23 오후 2 15 07

the question field should [object] that linked to QuestionViewType.Question.AsObject, but appears anything.

As you suggested, I think I should use json-schema-traverse to achieve my purpose.

julrich commented 2 years ago

You could give it a try, yeah! Let me know if it works!

And maybe the following snippet could help you:

const addExplicitAnyOfs = (schemaJson: JSONSchema7, schemaAnyOfs: JSONSchema7[]) => {
  traverse(schemaJson, {
    cb: (schema, pointer, rootSchema) => {
      if (schema.items && schema.items.anyOf) {
        const componentPath = rootSchema.$id.split('/');
        const componentType = path.basename(rootSchema.$id).split('.')[0];
        const componentName = uppercamelcase(componentType);

        schema.items.anyOf = schema.items.anyOf.map((anyOf: JSONSchema7) => {
          if (anyOf.$ref)
            return anyOf;

          const schemaName = `http://frontend.ruhmesmeile.com/${componentPath[3]}/${componentPath[4]}/${componentType}/${pointer.split('/').pop()}-${anyOf.title.replace(componentName, '').toLowerCase()}.interface.json`;
          schemaAnyOfs.push({
            $id: schemaName,
            $schema: "http://json-schema.org/draft-07/schema#",
            ...anyOf,
            definitions: schemaJson.definitions
          });
          return { $ref: schemaName };
        });
      }
    }
  });
}

It's not really doing what you need, but I think you could just modify it. This snippet takes all our anyOfs, and makes $refs out of them (e.g. { type: 'object', 'properties': { 'size': {...} } } gets turned into '$ref': 'sizeObject.schema.json'), I think you can use a similar approach to replace your recursive$ref`s.

About the inline $refs, forget that. I don't think that would really help or actually work!

remigailard80 commented 2 years ago

I'll try. thanks!