Search code examples
jsonrdfjson-ld

How do correct my JSON-LD syntax to give me the desired expansion?


Context

I am trying to collect a set of requirements and record them in JSON-LD format. The JSON-LD document will serve as a configuration file to drive automated data analysis in the short term. In the long term, my boss hopes to leverage the underlying RDF graphs of this configuration and other documents we are also generating in JSON-LD. Not sure what he plans to do exactly, but he's got a big brain so I'm just in charge of the short-term.

Problem/Question

Question in the title. I am getting twisted around the axel trying to figure out how to correctly expand property values into meaningful URI/URLs. It seems easy enough to expand property names, but I want the values of those properties to expand.

What I've tried

I've spent two days in the JSON-LD sandbox and this is what I have to show for my effort:

{
  "@context" : {   
    "number" : "locator:requirements/number/",
    "var"  : "locator:models/"
  },

  "@graph" : [
    {
      "@id" : "number:001",

      "target" : {
        "@type" : "float",
        "@value" : 22.6
      },

      "variable" : {
        "@id" : "var:StarTracker/AngleToSun"
      },
        
      "operator" : "minimum"
    }
  ]
}

Which I would like to expand to:

[
  {
    "@id": "locator:requirements/number/001",
    "operator": [
      {
        "@value": "minimum"
      }
    ],
    "target": [
      {
        "@type": "float",
        "@value": 22.6
      }
    ],
    "variable": [
      {
        "@id": "locator:models/StarTracker/AngleToSun"
      }
    ]
  }
]

Following the advice of this answer, I used @vocab to make some small progress. However, it appears that @vocab signifies a default URI, which gets applied to EVERYTHING, not just the bits that I want it to. With my new context

  "@context" : {   
    "@vocab" : "locator:requirements/number/",
    "number" : "locator:requirements/number/",
    "var"  : "locator:models/"
  }

I now get this expansion, with lots of stuff that I don't want.

[
  {
    "@id": "locator:requirements/number/001",
    "locator:requirements/number/operator": [ <------------ UNWANTED EXPANSION
      {
        "@value": "minimum"
      }
    ],
    "locator:requirements/number/target": [ <-------------- UNWANTED EXPANSION
      {
        "@type": "locator:requirements/number/float", <---- UNWANTED EXPANSION
        "@value": 22.6
      }
    ],
    "locator:requirements/number/variable": [ <------------ UNWANTED EXPANSION
      {
        "@id": "locator:models/StarTracker/AngleToSun"
      }
    ]
  }
]

I realize now that @vocab is a "default" expansion, and so is not what I should be using. How do I do this the right way?


Solution

  • In order for JSON-LD to be interoperable with RDF, you need a mapping from your properties to URIs. In your original example, the properties operator, target, and variable (and the datatype float) are not mapped to any URI, and so are effectively ignored by the JSON-LD processor.

    The common solution is to define all vocabulary items through the @context, either directly or in the file it points to. In your case, that would be:

    {
      "@context": {   
        "number": "locator:requirements/number/",
        "var": "locator:models/",
        
        "operator": "locator:schema/operator",
        "target": "locator:schema/target",
        "variable": "locator:schema/variable",
        "float": "locator:schema/float"
      },
    
      "@graph": [
        {
          "@id": "number:001",
          
          "target": {
            "@type": "float",
            "@value": 22.6
          },
    
          "variable": {
            "@id": "var:StarTracker/AngleToSun"
          },
          
          "operator": "minimum"
        }
      ]
    }
    

    It is advantageous to define all items explicitly, as it helps to detect when you use undefined items, and you can provide additional context for properties to shorten your syntax even more, such as with this:

    {
      "@context": {   
        "number": "locator:requirements/number/",
        "var": "locator:models/",
        
        "variable": {
          "@id": "locator:schema/variable",
          "@type": "@id"
        }
      },
    
      "@graph": [
        {
          "@id": "number:001",
    
          "variable": "var:StarTracker/AngleToSun"
        }
      ]
    }
    

    By making the type of variable @id, you don't need to write it out fully every time.

    Another solution is to use a prefix for every item:

    {
      "@context": {   
        "number": "locator:requirements/number/",
        "var": "locator:models/",
        
        "s": "locator:schema/"
      },
    
      "@graph": [
        {
          "@id": "number:001",
          
          "s:target": {
            "@type": "s:float",
            "@value": 22.6
          },
    
          "s:variable": {
            "@id": "var:StarTracker/AngleToSun"
          },
          
          "s:operator": "minimum"
        }
      ]
    }
    

    This has the same interpretation as before, but without any explicit definition of items, and it requires modification of the JSON part.

    Lastly, you could easily use @vocab here too, again to the same interpretation:

    {
      "@context": {   
        "number": "locator:requirements/number/",
        "var": "locator:models/",
        
        "@vocab": "locator:schema/"
      },
    
      "@graph": [
        {
          "@id": "number:001",
          
          "target": {
            "@type": "float",
            "@value": 22.6
          },
    
          "variable": {
            "@id": "var:StarTracker/AngleToSun"
          },
          
          "operator": "minimum"
        }
      ]
    }
    

    If you see bits that you don't want @vocab to apply to, you need to define them as a URI anyway@vocab simply specifies the default namespace for everything that you left unmapped, but that means it needed mapping in the first place.

    Which solution to use? Preferably all of them ‒ for the most common properties, define them explicitly, with a datatype and potentially other information. For the other more "exotic" properties or properties from other vocabularies (that you might not need to process that much through just JSON), use a prefix. If you have a namespace that you cannot define this way, use @base if it is in @id, and @vocab everywhere else.