Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep $ref some how when bundle #173

Closed
wenerme opened this issue May 10, 2020 · 13 comments
Closed

Keep $ref some how when bundle #173

wenerme opened this issue May 10, 2020 · 13 comments

Comments

@wenerme
Copy link

wenerme commented May 10, 2020

Because $ref offen mean type of something, when render views, need the ref type to select the component.

e.g.

Address.json

{
  "type": "object",
  "properties": {
    "a": {
      "type": "string"
    }
  }
}

User.json

{
  "type": "object",
  "properties": {
    "address": {
      "$ref":"Address.json"
    }
  }
}

If the bundled result is

{
  "type": "object",
  "properties": {
    "address": {
      "_ref": "Address.json",
      "type": "object",
      "properties": {
        "a": {
          "type": "string"
        }
      }
    }
  }
}

Then the frontend can use the "_ref": "Address.json" to match the component more precisely.

@JamesMessinger
Copy link
Member

There's already some existing discussion going on about this in Issue #135. I think your solution could be acceptable, but I'd like to get some other opinions too

@maxtuzz
Copy link

maxtuzz commented May 26, 2020

Indeed this looks good to me. Though perhaps persisting $ref instead of _ref would be preferred?

+1

@philsturgeon
Copy link
Member

Persisting $ref would confuse tools which know what a $ref is and want to do something about it. It could cause some infinite loops, or just errors from tools which warn you they don't know how to deal with a $ref - which is why you're dereferencing in the first place.

@MikeRalphson has done a bit of this.

@MikeRalphson
Copy link

I simply allowed the caller to specify a property name for it to be copied to if they wanted $ref preservation.

@JamesMessinger
Copy link
Member

I like @MikeRalphson's approach. We could add an option, like say dereference.$refRename that defaults to false.

Any falsy value = remove the $ref as it does currently.

Any string value = rename the $ref to the specified value

@ftheomunhoz
Copy link

That would definitely work in our case (from Issue #135) and wouldn't generate a breaking change.

@bcherny
Copy link
Contributor

bcherny commented Jul 23, 2020

Author of json-schema-to-typescript here. We'd love this features, as it would unblock a handful of issues related to emitting more intelligent TypeScript types.

As specific example, consider the following JSON Schema:

{
  additionalProperties: false,
  definitions: {
    a: {type: 'string'},
    b: {type: 'number'}
  },
  properties: {
    c: {
      additionalItems: false,
      items: [{$ref: '#/definitions/a'}, {$ref: '#/definitions/b'}],
      type: 'array'
    }
  },
  title: 'X,
  type: 'object'
}

We'd like to emit the following TypeScript types for this schema:

type A = string
type B = number

type X = {
  c: [] | [A] | [A, B]
}

Today, we accomplish this by always emitting standalone type names (here, A and B) for tuple members. However, this isn't the right behavior; it would be nice to refine our heuristic, where we look for the presences of $refs as a sign that a type is meant to be reused, and therefore, named (we already use ids and titles for this today).

To do that, we'd love a way to know what a schema's original $ref pointed to. Any key name is fine for us, though you might consider using a Symbol in order to avoid a backwards-incompatible change to this library, and to avoid colliding with existing keys.

As a side note: It's possible to infer something like this today (roughly: traverse over a dereferenced schema and its source schema at the same time, and annotate the dereferenced schema where you see a $ref in its source schema). But this is a hack that doesn't work when a source node is in another file, since $RefParser.resolve(..).get(..) returns a dereferenced schema, not a source schema.

Please let me known if this is not something you're interested in, and I'll go ahead and fork json-schema-ref-parser so we can patch it in for our use case.

@JamesMessinger
Copy link
Member

@bcherny - Thanks for providing another example of why this feature would be useful. If somebody would like to submit a PR, I'd be happy to review it.

However, I've realized that it's not actually as easy as I suggested previously. The problem with simply keeping the $ref property (or renaming it) is that the same object can be referenced in many different places, so which $ref do you keep?

Instead, it seems like we may need to add an array property (probably with a Symbol to avoid breaking changes) that contains all the refs that pointed to a particular object.

@bcherny
Copy link
Contributor

bcherny commented Jul 24, 2020

The problem with simply keeping the $ref property (or renaming it) is that the same object can be referenced in many different places, so which $ref do you keep?

@JamesMessinger That's a great catch!

Thinking about it more, I wonder if a simpler and more general-purpose approach might be to avoid setting a property at all, and exposing an onDereference hook instead.

One possible API:

const result = await $RefParser.dereference('schema.json', {
  onDereference(originalSourceSchema, dereferencedSchema, originalRefPath, absoluteRefPath) {
    // ...
  }
})

A consumer might then use this API to build up a mapping:

const map = new Map

const result = await $RefParser.dereference('schema.json', {
  onDereference(originalSourceSchema, dereferencedSchema, originalRefPath, absoluteRefPath) {
    if (!map.has(dereferencedSchema)) {
      map.set(dereferencedSchema, new Set)
    }
    map.get(dereferencedSchema).add(originalRefPath)
  }
})

This approach has a few benefits over the Symbol-based approach:

  • It's strictly backwards-compatible, while I can come up with some convoluted cases where the Symbol-based approach is not
  • It more closely sticks to the JSON-Schema spec, and avoids having to monkey-patch in properties specific to json-schema-ref-parser (in practice, this would mostly affect consumers that use TypeScript)
  • It supports use cases that the Symbol-based approach does not (say, also collecting the original $ref-containing schemas)
  • It's extensible without additional breaking changes. eg. down the line we could introduce a 5th parameter for the onDereference callback, without breaking clients (not so for the Symbol-based approach)

@JamesMessinger
Copy link
Member

I like that idea @bcherny! What does everyone else think? Would this work for your use-case?

/cc @wenerme @maxtuzz @MikeRalphson @philsturgeon @ftheomunhoz

@MikeRalphson
Copy link

Personally, I've only ever needed to know where a $ref used to be and not what locations previously pointed into a $refed component, and although using a callback would be a little more work (maintaining and comparing to a list of locations as you traversed the dereferenced object, or decorating it yourself), it certainly has the flexibility to enable those use-cases.

@philsturgeon
Copy link
Member

This callback approach would work for the "live mock server wants to watch all $ref files for changes so it can reload" use-case. We'd just look file all filepaths and strip of anchors and watch that set of files.

@philsturgeon
Copy link
Member

All conversations about changing how $ref should work should be done over here https://github.com/json-schema-org/referencing then when its decided we can all make sure tools follow it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants