[MGNLREST-161] Delivery endpoint can return subnodes as an array Created: 15/Dec/17  Updated: 04/Apr/22

Status: Accepted
Project: Magnolia REST Framework
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Neutral
Reporter: Richard Gange Assignee: Unassigned
Resolution: Unresolved Votes: 7
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
duplicate
is duplicated by MGNLREST-200 Delivery endpoint returns arrays of n... Closed
relation
Template:
Acceptance criteria:
Empty
Release notes required:
Yes
Documentation update required:
Yes
Date of First Response:
Epic Link: Headless Backlog
Story Points: 8

 Description   

Create a configuration option to allow subnodes to be returned as an array of nodes

User story:

As a SPA developer, I can get node content returned directly in arrays, so that I can work with content in a familiar and productive way. 

Notes:

Currently the delivery endpoint returns subnodes directly as properties on the parent node, and then a '@nodes' array which includes the names of all of those properties. While this can have advantages when a developer is trying to access a specific node, (because they can just get it by its name) it makes the usecase of accessing lists of subnodes (such as areas, pages, blocks) harder because it adds a level of indirection. Practically in a React or Angular app this can be awkward. So much so that developers have asked us how to iterate over the nodes.

Acceptance criteria:

  • A  way to configure a delivery endpoint to return arrays of subnodes.
  • Ideally in a way that also still allows access to "non-list" subnodes by name.
  • Ideally in a way that nodes with two types of children can generate more than one array.

Suggestion:

Add the ability to define which nodetypes should be returned in arrays. Any node that is not in one of the specified types is still returned as a direct subnode.

So if I specify:

workspace: website
arrays:
  - pages:
    nodeType: mgnl:page
  - areas:
    nodeType: mgnl:area

Then the content will include a 'pages' and 'areas' arrays, which includes the subnodes contents. And any subnode NOT included in those arrays will still be included directly on the parent object, and still be listed in the @nodes array.

 

 

 

 



 Comments   
Comment by Richard Gange [ 18/Dec/17 ]

Example:

From:

{
   "@name": "0",
   "@path": "/jetBlue/header/0",
   "TrueBlueNrtxt": "TrueBlue #",
   "profileLinks": { *{color:red}//MultiValue Field{color}*
      "@name": "profileLinks",
      "@path": "/jetBlue/header/0/profileLinks",
      "0": {
         "@name": "0",
         "@path": "/jetBlue/header/0/profileLinks/0",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/my-trueblue-home",
         "LinkText": "Dashboard",
         "@nodes": []
      },
      "1": {
         "@name": "1",
         "@path": "/jetBlue/header/0/profileLinks/1",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/account-info",
         "LinkText": "My Profile",
         "@nodes": []
      },
      "2": {
         "@name": "2",
         "@path": "/jetBlue/header/0/profileLinks/2",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/activity-history",
         "LinkText": "My Activity",
         "@nodes": []
      },
      "3": {
         "@name": "3",
         "@path": "/jetBlue/header/0/profileLinks/3",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/our-partners",
         "LinkText": "Our Partners",
         "@nodes": []
      },
      "4": {
         "@name": "4",
         "@path": "/jetBlue/header/0/profileLinks/4",
         "linkType": "external",
         "linkHref": "https://badges.jetblue.com/",
         "LinkText": "My Badges",
         "@nodes": []
      },
      "5": {
         "@name": "5",
         "@path": "/jetBlue/header/0/profileLinks/5",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/current-deals",
         "LinkText": "CURRENT DEALS",
         "@nodes": []
      },
      "6": {
         "@name": "6",
         "@path": "/jetBlue/header/0/profileLinks/6",
         "linkType": "external",
         "linkHref": "https://www.barclaycardus.com/",
         "LinkText": "VIEW CREDITS",
         "@nodes": []
      },
      "7": {
         "@name": "7",
         "@path": "/jetBlue/header/0/profileLinks/7",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/trueblue-faqs",
         "LinkText": "FAQS",
         "@nodes": []
      },
      "8": {
         "@name": "8",
         "@path": "/jetBlue/header/0/profileLinks/8",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/c/portal/logout",
         "LinkText": "SIGN OUT",
         "@nodes": []
      },
      "@nodes": [
         "0",
         "1",
         "2",
         "3",
         "4",
         "5",
         "6",
         "7",
         "8"
      ]
   },
   "@nodes": [
      "profileLinks"
   ]
}

To:

{
    "@name": "0",
    "@path": "/jetBlue/header/0",
    "TrueBlueNrtxt": "TrueBlue #",
    "profileLinks": { *{color:red}// MultiValueFiled entires as array in @nodes{color}*
        "@name": "profileLinks",
        "@path": "/jetBlue/header/0/profileLinks",
        "@nodes": [
            {
                "@name": "0",
                "@path": "/jetBlue/header/0/profileLinks/0",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/my-trueblue-home",
                "LinkText": "Dashboard",
                "@nodes": []
            },
            {
                "@name": "1",
                "@path": "/jetBlue/header/0/profileLinks/1",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/account-info",
                "LinkText": "My Profile",
                "@nodes": []
            },
            {
                "@name": "2",
                "@path": "/jetBlue/header/0/profileLinks/2",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/activity-history",
                "LinkText": "My Activity",
                "@nodes": []
            },
            {
                "@name": "3",
                "@path": "/jetBlue/header/0/profileLinks/3",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/our-partners",
                "LinkText": "Our Partners",
                "@nodes": []
            },
            {
                "@name": "4",
                "@path": "/jetBlue/header/0/profileLinks/4",
                "linkType": "external",
                "linkHref": "https://badges.jetblue.com/",
                "LinkText": "My Badges",
                "@nodes": []
            },
            {
                "@name": "5",
                "@path": "/jetBlue/header/0/profileLinks/5",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/current-deals",
                "LinkText": "CURRENT DEALS",
                "@nodes": []
            },
            {
                "@name": "6",
                "@path": "/jetBlue/header/0/profileLinks/6",
                "linkType": "external",
                "linkHref": "https://www.barclaycardus.com/",
                "LinkText": "VIEW CREDITS",
                "@nodes": []
            },
            {
                "@name": "7",
                "@path": "/jetBlue/header/0/profileLinks/7",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/group/trueblue/trueblue-faqs",
                "LinkText": "FAQS",
                "@nodes": []
            },
            {
                "@name": "8",
                "@path": "/jetBlue/header/0/profileLinks/8",
                "linkType": "external",
                "linkHref": "https://trueblue.jetblue.com/c/portal/logout",
                "LinkText": "SIGN OUT",
                "@nodes": []
            }
        ]
    },
    "@nodes": [
        "profileLinks"
    ]
}
Comment by Mikaël Geljić [ 07/May/18 ]

FYI, the current format (child-objects + array of keys) was researched and done on purpose, for the lack of any information about the model.
We don't know when an array would be preferred over sub-objects, or vice-versa; so we chose the approach which let us do both in the least opinionated way:

  • Direct access to sub-node => page.subpage.foo
  • Iterating over child-nodes, in order => for (subpage of page.@nodes) ...

Hope that helps, by the time we get proper content type modeling.

Comment by Antonio Tuor [ 18/Jun/18 ]

At the moment, it's possible to change/extend the interface, to add additional configuration.
However, it's not possible to override/extend the implementation class. (No documentation about this in the docu and my try to override the implementationClass didn't work)
Therefore, it's not possible to make this adaption manually in the back-end. But this should be easy, as there is only the need, to change the the ContentDecoratorNodeWrapper class, to extend ExtendingNodeWrapper instead of the DelegateNodeWrapper (which is already extended by the ExtendingNodeWrapper class).

With the current implementation of seperating the keys from the values, it's hard to traverse multiple nodes. This would be much easier by having an array of nodes or an map of <key, node> to traverse.

Comment by Will Scheidegger [ 27/Jun/18 ]

Make it configurable please! The current behaviour is a very good "default" implementation. But I would suggest, that you add support for a "childNodes" configuration:

childNodeTypes:
  - profileLink
  - connection
childNodes:
  - profileLinks:
    isList: true
    listName: bookmarks

 
This would then produce an output like

{
   "@name": "0",
   "@path": "/jetBlue/header/0",
   "TrueBlueNrtxt": "TrueBlue #",
   "bookmarks": [
      {
         "@name": "0",
         "@path": "/jetBlue/header/0/profileLinks/0",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/my-trueblue-home",
         "LinkText": "Dashboard",
      },
      {
         "@name": "1",
         "@path": "/jetBlue/header/0/profileLinks/1",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/account-info",
         "LinkText": "My Profile",
      },
      {
         "@name": "2",
         "@path": "/jetBlue/header/0/profileLinks/2",
         "linkType": "external",
         "linkHref": "https://trueblue.jetblue.com/group/trueblue/activity-history",
         "LinkText": "My Activity",
      }
   ],
   "connections": {
      "@name": "connections",
      "@path": "/jetBlue/header/0/connections",
      "0": {
         "@name": "0",
         "@path": "..."
      },
      "1": {
         "@name": "1",
         "@path": "..."
      },
      "@nodes": [
         "0",
         "1"
      ]
   }
}

So if in childNodes you have a nodeName profileLinks which is set to isList: true, then the child nodes of the "profileLinks" node would be represented as list instead of a map. Optionally you could also modify the name of the list (in the example above the jcr node name "profileLinks" is renamed to "bookmarks" in the JSON output. For the "connections", no such setting is made. Therefore the standard data representation is used.

I tried to do a little POC with a custom node writer. Unfortunately I could not get RestEasy to my writer as the class I sent it was always wrapped by the I18NContainerResponseFilter...

Comment by Mikaël Geljić [ 27/Jun/18 ]

Hi will, sounds reasonable. If you know for sure that you don't have any mixed child-collection of different types in specific places, why not letting you have it that way.

Comment by Will Scheidegger [ 28/Jun/18 ]

Cool! When you're worried about mixed child nodes, you could add a "nodeType" property to that childNodes list configuration that would then filter the child nodes for a specified type.

Comment by Christopher Zimmermann [ 13/Nov/19 ]

Bringing in information from MGNLREST-200 (including its storypoints estimate)

Generated at Mon Feb 12 06:57:13 CET 2024 using Jira 9.4.2#940002-sha1:46d1a51de284217efdcb32434eab47a99af2938b.