Search code examples
javascriptneo4jcypherneo4j-apoc

Neo4j - is there a way to destruct json objects like in javascript


Working with nodejs and neo4j. I have the node-label: Product. one of it's properties is entries: which is a stringfied json, that contain nested objects of type - entry.

when ever a user enter a product link, the amount of it's entry is incremented.

e.g: Entering the product link from Facebook page, amount of facebookPage entry should be incremented.

(productId and entry are arguments of the server-endpoint that route to that query. ) the current query:

MATCH (p:Product {id: $prodcutId})
WITH apoc.convert.fromJsonMap(p.entries).facebookPage AS jsonEntries, p
SET p.entries = apoc.convert.toJson({facebookPage: { link: jsonEntries.link, amount: jsonEntries.amount + 1}})
RETURN p as product

with one entry (facebookPage), the query is working fine.

but with more than one(e.g: instagramPage), i need a way to save the former entries data.

with javascript i would have done something like this:

SET p.entries = apoc.convert.toJson({...jsonEntries, $entry: { link: jsonEntries.link, amount: jsonEntries.amount + 1, min: 1 }}})

Is there a way to achieve this behavior ?

i saw the APOC dot notation for destructing json object.

link to the docs

using it with my case, it would look something like this

MATCH (p:Product {id: 'b80a61ea4a40408f847214fa3ccf9067'})
WITH apoc.convert.fromJsonMap(l.entries) AS jsonEntries, l
SET l.entries = apoc.convert.toJson(jsonEntries{.instagramPage, facebookPage: { link: jsonEntries.facebookPage.link, amount: jsonEntries.amount + 1 }})
RETURN l as p

but this requires specifying any of the entries, which isn't desired. There will be a lot of entries, and it will make the query hard to maintain. also, the query will need to be updated any time there is a new entry.

product structure:

{
"entries": "{"facebookPage":{"amount":1,"link":"www.facebook.com"},"instagram":{"amount":1,"link":"www.IG.com"}}",
"id": "b80a61ea4a40408f847214fa3ccf9067",
"title": "Guitar"
  }
}

entry structure:

{
  amount: 0,
  link: 'some-link.com',
}

Solution

  • The destructuring you're using there isn't an APOC feature but just vanilla Neo4j. You can destructure all properties using the .* selector - see the last example on the map projection documentation page.

    For you then, we'd replace .instagramPage with .*:

    MATCH (p:Product {id: 'b80a61ea4a40408f847214fa3ccf9067'})
    WITH apoc.convert.fromJsonMap(l.entries) AS jsonEntries, l
    SET l.entries = apoc.convert.toJson(jsonEntries{.*, facebookPage: { link: jsonEntries.facebookPage.link, amount: jsonEntries.amount + 1 }})
    RETURN l as p
    

    Here's a minimal example showing .* working just to play around with:

    WITH {instagramPage: {link: "instagram.com"}} AS entry
    RETURN entry {.*, facebookPage: {link: "facebook.com"}}
    
    Output:
    {
      "facebookPage": {
        "link": "facebook.com"
      },
      "instagramPage": {
        "link": "instagram.com"
      }
    }
    

    Happily, destructuring this way also replaces existing fields in the map with updated values when there's a collision:

    WITH {instagramPage: {link: "instagram.com"}} AS entry
    RETURN entry {.*, instagramPage: {link: "newinstagram.com"}}
    
    Output:
    {
      "instagramPage": {
        "link": "newinstagram.com"
      }
    }