digitalbazaar / jsonld.js

A JSON-LD Processor and API implementation in JavaScript
https://json-ld.org/
Other
1.64k stars 195 forks source link

JSON Array of length 1 gets compacted into non array #509

Open adlerfaulkner opened 1 year ago

adlerfaulkner commented 1 year ago

When compacting (or framing with an @context), any @value of a node with "@type': "@json" that is a JSON array of length 1 gets transformed into a non array value equalling the first item in the JSON array.

For example:

const doc = {
  "https://example.com/people": {
    "@type": "@json",
    "@value": [ { "name": "Jim" } ]
  }
};

const context = {
  "people": {
    "@id": "https://example.com/people",
    "@type": "@json"
  }
};

const compacted = await jsonld.compact(doc, context);
console.log(JSON.stringify(compacted, null, 2));
/* Output:
{
  "@context": {
    "people": {
      "@id": "https://example.com/people",
      "@type": "@json"
    }
  },
  "people": {
    "name": "Jim"
  }
}
*/

Note above that the people field in the output was transformed from a JSON array to an object equal the the original array's first value.

I would expect this library to preserve the original JSON array. The output should be:

{
  "@context": {
    "people": {
      "@id": "https://example.com/people",
      "@type": "@json"
    }
  },
  "people": [
    { "name": "Jim" }
  ]
}
adlerfaulkner commented 1 year ago

Reference to relevant part of the JSON-LD spec https://www.w3.org/TR/json-ld11/#json-literals

At times, it is useful to include JSON within JSON-LD that is not interpreted as JSON-LD. Generally, a JSON-LD processor will ignore properties which don't map to IRIs, but this causes them to be excluded when performing various algorithmic transformations. But, when the data that is being described is, itself, JSON, it's important that it survives algorithmic transformations.

adlerfaulkner commented 1 year ago

Ah, I am seeing from this test https://w3c.github.io/json-ld-api/tests/compact-manifest#tjs07 that you have to add "@container": "@set" to the context.

That seems like a requirement that is not stated in the spec, and in fact contradicts the spec's notion that JSON literals should not be transformed by the processor in any way. Any one have insight on why this is?

dlongley commented 1 year ago

@adlerfaulkner,

This might be a good issue to bring the spec as this is just a particular implementation of it. You can do so over here:

https://github.com/w3c/json-ld-api/issues

davidlehn commented 1 year ago

The spec and test coverage of this area is poor. I think your example should by default output the JSON array: "people": [{...}]. If @set is used, it should output an array with your JSON: "people": [[{...}]]. If that's the case, then js07 is incorrect.

There are complications here too. If multiple JSON values are compacted, it would be fine if @set is in the context. But if not, what should happen? It can't make an array, since that would be interpreted as raw JSON. I think compaction should throw an error. But the spec and tests don't cover this case. There is also the oddity of JSON + non-JSON values for the same term. If there is a single JSON value, then jsonld.js, probably by luck, will function and output the JSON in the compacted term, and non-JSON as values for the expanded term. That's arguably a way to do it, but I'm not sure it's explicit anywhere. And also how do you interpret data that has @set in the context but the value is not an array? Throw an error or just fallback and assume a single JSON value?

I'm working up some tests for the WG to look at and clarify how this should work, and jsonld.js fixes will follow.

gkellogg commented 1 year ago

Yes, the intersection of "@container": "@set" and "@type": "@json" could probably be explored further. Looking at the expansion algorithm, the value of a term with @type: @json will always be extracted from any containing array, so the JSON value can't really take the form of an array (probably could if it were the value of @value, though).

I'm sure there are some unexplored corner-cases here.

adlerfaulkner commented 1 year ago

Thanks @gkellogg and @davidlehn. I look forward to seeing those tests and their reception in the WG.

It sounds to me like @set in the context should always be evaluated before determining the type of the value (JSON or non-JSON). I think what you say about having @set in the context for a non-Array value during compaction should either throw an error or just not match (& thus not compact) the field. This would be similar to how a field would not be compacted if the @type field was not matching even though its id field matched between context and data.

gkellogg commented 1 year ago

@container: @set isn't used in expansion; it is a signal for the compaction algorithm to put values inside an array. Note that the compaction algorithm can't generate an empty array, as there is no value to invoke in the algorithm.

Creating more special magic for @set along with @type: @json would be special use and complicate an already complicated process, so personally, I don't see that happening.

Feel free to raise an issue at https://github.com/w3c/json-ld-api, but I don't see that making the cut for the next version.

davidlehn commented 1 year ago

Issue: https://github.com/w3c/json-ld-api/issues/560 First pass at test cases: https://github.com/w3c/json-ld-api/pull/559