json5 / json5-spec

The JSON5 Data Interchange Format
https://spec.json5.org
MIT License
49 stars 11 forks source link

allow self-referencing #32

Closed ghost closed 5 months ago

ghost commented 3 years ago

something like (this allows for self-recursion too)


{
 "firstName": "Joe",
 "lastName": "Ress",
 "name": $self.firstName .. $self.lastName,
 "recursion": {
  $self.recursion,
  $self
 }
}
jordanbtucker commented 3 years ago

Thanks for the suggestion. Do you have any use cases for this feature?

ghost commented 3 years ago

well the example in the OP "name": $self.firstName .. $self.lastName, would be useful

jordanbtucker commented 3 years ago

I guess I was more asking: Why do you need this feature? What problem are you trying to solve?

Azmisov commented 5 months ago

Despite the current downvotes, I think this would actually be a great addition. YAML files support this via &/*/<< operators, so wherever you use such YAML configurations, you could use JSON5 instead. Substitutions are quite useful in configurations, e.g. docker service files: the same configuration needs to be repeated, with slight variations, across many services. It depends what the goal of JSON5 is. Are we just trying to add minor syntactic sugar to plain JSON files? or are we open to adding extra, more powerful features?

// both the following...
{
  shared: { a: 10 },
  service: $self.shared
}
{
  shared: { a: 10 },
  service: { $self.shared }
}
// resolve to
{
  shared: { a: 10 },
  service: { a: 10 }
}

An idea is to allow a deep merge when prefixed by $$. YAML doesn't support deep merges, so would be an improvement over them. This would allow array merges as well, which YAML also doesn't support. For example:

// object deep merge
{
  shared: {a:5, b:{c:10}}
  service: {b:{d:15}, $$self.shared}
}
{
  shared: {a:5, b:{c:10}}
  service: {b:{d:15,c:10}, a:5} 
}
// array deep merge
{
  shared: [0,1,2],
  service: [3,4,$$self.shared,5]
}
{
  shared: [0,1,2],
  service: [3,4,0,1,2,5]
}

One complaint I've had working with YAML is that it isn't possible to override nested values when merging. An override always applies on a shallow level. For example, suppose I have this config:

shared: &shared
    parent:
        a: 5
        b: 9
service:
    << *shared
    parent:
        b: 6

In this case, service.parent overwrites that of shared.parent, rather than merging them in a deep manner. Unfortunately, a deep merge wouldn't really help here: we want non-referenced service to merge with the referenced shared, not the other way around.

However, another idea is to support some very basic templating in JSON5. The implementation would be able to reuse the same backend as is used for regular references/substitution. For example:

{
  someConstant: 10,
  // define the template function
  shared:(x,y) {
    sibling: $self.someConstant,
    parent: {
      child: $x,
      $$y
    }
  }
  // use template like a regular reference, but pass variables
  service: {
    $self.shared("foo", {c:6})
  }
}
{
  // (result trimmed down)
  service: {
    sibling: 10,
    parent: {
      child: "foo",
      c: 6
    }
  }
}

Note the simplicity in that $self references top level JSON paths, while $x and $$y in this example reference the variables from the template call stack. So the reference substitution would behave identically, except in where it looks for the variables to be substituted. I think these simple template substitutions would make JSON5 quite powerful and open up a lot more use cases. In the future we could think about adding even more templating features, like repeating some template call N times, mapping over a list of parameters.

jordanbtucker commented 5 months ago

Thanks for your input. While this is a very interesting suggestion, one of the things that makes JSON so easy to use and implement is its simplicity. We follow the same principle in JSON5. It's already significantly more complex than JSON, so adding this feature is against the core principles of JSON5.