Search code examples
gremlin

Gemlin query to output a tree to a specific format


First off, I'd like to say I've very new to this so apologies for any clangers!

I have a fairly simple hierarchy of recipes and ingredients. I'm looking to create a query that outputs something like this:

{
  "id": "sandwich-a",
  "name": "Sandwich A",
  "composedOf": [
    {
      "id": "tomato",
      "name": "Tomato",
      "weight": 40,
      "composedOf": []
    },
    {
      "id": "sauce-a",
      "name": "Sauce A",
      "weight": 2.8,
      "composedOf": [
        {
          "id": "salt",
          "name": "Salt",
          "weight": null,
          "composedOf": []
        },
        {
          "id": "pepper",
          "name": "Pepper",
          "weight": null,
          "composedOf": []
        }
      ]
    },
    {
      "id": "sauce-b",
      "name": "Sauce B",
      "weight": null,
      "composedOf": [
        {
          "id": "water",
          "name": "Water",
          "weight": null,
          "composedOf": []
        },
        {
          "id": "lemon-juice",
          "name": "Lemon Juice",
          "weight": null,
          "composedOf": []
        }
      ]
    }
  ]
}

The id and name are properties on the vertex.
weight is a property on the edge that may or may not be present.
composedOf is a collection of vertices connected by edges with the label composed-of.

This is the query I have so far:

g.V()
    .has("id", "new-york-ciabatta")
    .project("id", "name", "composedOf")
        .by("id")
        .by("name")
        .by(
            repeat(
                out("composed-of")
                .outE().as('e')
                .inV().as('v')
                .simplePath()
                )
            .emit()
            .tree())

It works but it's sending a lot of additional data. How can I get it to output JSON similar to above?

Gremlify example: https://gremlify.com/dxy4uwpan7n/2


Solution

  • I had to tweak the sample graph data provided, and the query to get it to work. Here is the graph data I used having exported it from Gremlify and modified it:

    g.addV('recipe').as('1').property(single, 'name', 'New York Ciabatta').property(single, 'clientId', 'XYZ').
      addV('ingredient').as('2').property(single, 'name', 'Water').property(single, 'clientId', 'XYZ').
      addV('ingredient').as('3').property(single, 'name', 'Salt').property(single, 'clientId', 'XYZ').property(single, 'saltContent', 100).
      addV('ingredient').as('4').property(single, 'name', 'Pepper').property(single, 'clientId', 'XYZ').
      addV('ingredient').as('5').property(single, 'name', 'Mustard').property(single, 'clientId', 'XYZ').
      addV('ingredient').as('6').property(single, 'name', 'Paprika').property(single, 'clientId', 'XYZ').
      addV('compound-ingredient').as('7').property(single, 'name', 'American Mustard').property(single, 'clientId', 'XYZ').
      addV('compound-ingredient').as('8').property(single, 'name', 'Cajun Seasoning Mix').property(single, 'clientId', 'XYZ').property(single, 'saltContent', 28.7).
      addE('composed-of').from('1').to('7').
      addE('composed-of').from('7').to('2').
      addE('composed-of').from('7').to('3').property('weight', 2.9).
      addE('composed-of').from('7').to('5').
      addE('composed-of').from('8').to('3').
      addE('composed-of').from('8').to('6').
      addE('composed-of').from('8').to('4')
    

    The tree step, can take a by modulator, which allows you to specify which fields to include in the output. So for example, to reduce the amount of information, you could try something like:

    g.V().
      has("name", "New York Ciabatta").
      repeat(outE("composed-of").inV().simplePath()).
      until(not(out())).
      tree().
        by('name').
        by(label)
    

    When run using the Gremlin Console

    gremlin> g.V().
    ......1>   has("name", "New York Ciabatta").
    ......2>   repeat(outE("composed-of").inV().simplePath()).
    ......3>   until(__.not(out())).
    ......4>   tree().by('name').by(label)
    
    ==>[New York Ciabatta:[composed-of:[American Mustard:[composed-of:[Water:[],Salt:[],Mustard:[]]]]]]
    

    To return the edge weight property instead of the label

    gremlin> g.V().
    ......1>   has("name", "New York Ciabatta").
    ......2>   repeat(outE("composed-of").inV().simplePath()).
    ......3>   until(__.not(out())).
    ......4>   tree().by('name').by('weight')
    
    ==>[New York Ciabatta:[American Mustard:[Water:[],2.9:[Salt:[]],Mustard:[]]]]
    

    to include the weight and the label

    gremlin> g.V().
    ......1>   has("name", "New York Ciabatta").
    ......2>   repeat(outE("composed-of").inV().simplePath()).
    ......3>   until(__.not(out())).
    ......4>   tree().by('name').by(union(values('weight'),label()).fold())
    
    ==>[New York Ciabatta:[[composed-of]:[American Mustard:[[2.9,composed-of]:[Salt:[]],[composed-of]:[Water:[],Mustard:[]]]]]]