Quite often in Sitecore development you have to work with hierarchical item structures, which resemble your data model. Example could be a multi-level menu or, as in my case, forms. At the time of writing, out-of-the-box Sitecore JSS is not able to serialize item structures with arbitrary number of levels deep into a JSON tree. Luckily, like the most of Sitecore functionality, JSS is easily customizable šŸ˜‰

The use case

To be more concrete, letā€™s focus on the multi-level menu example, where
the amount of levels in the hierarchy is arbitrary. Letā€™s say we have a datasourceĀ  item structure like this:

Multi-level hierarchical item structure

Sitecore JSS comes with 5 out-of-the-box rendering contents resolvers. They are well described in here Customizing Layout Service Rendering Output , together with the information on how to implement a custom one. The Folder Filter ResolverĀ can serialize descendants of the datasource item, but it excludes folders and the result is just a ā€œflatā€ array of items. For the above-mentioned example, the Layout Service output for the Menu rendering would be (some fields are omitted for simplicity):

{
   "uid":"ff8e58f5-a7c7-484b-b9be-e1c2c63b0fb5",
   "componentName":"Menu",
   "dataSource":"{C3382D8D-3048-41D9-B3FC-80D037476BCD}",
   "fields":{
      "items":[
         {
            "name":"Home",
            "fields":{ ... }
         },
         {
            "name":"Categories",
            "fields":{ ... }
         },
         {
            "name":"Sitecore",
            "fields":{ ... }
         },
         {
            "name":"JSS",
            "fields":{ ... }
         },
         {
            "name":"DevOps",
            "fields":{ ... }
      ]
   }
}

As youĀ  can see, menu items in the JSON output are flattened into an array, however, what I would like to achieve is (some fields are omitted for simplicity):

{
   "uid":"ff8e58f5-a7c7-484b-b9be-e1c2c63b0fb5",
   "componentName":"Menu",
   "dataSource":"{C3382D8D-3048-41D9-B3FC-80D037476BCD}",
   "fields":{
      "items":[
         {
            "name":"Home",
            "fields":{...}
         },
         {
            "name":"Categories",
            "fields":{
               "items":[
                  {
                     "name":"Sitecore",
                     "fields":{
                        "items":[
                           {
                              "name":"JSS",
                              "fields":{...}
                           }
                        ]
                     }
                  },
                  {
                     "name":"DevOps",
                     "fields":{...}
                  }
               ]
            }
         }
      ]
   }
}

To accomplish this, I have implemented a custom Rendering Contents Resolver, which is able to serialize arbitrary hierarchies to a tree.

Implementing the resolver

Step 1: implement the code

Create a custom rendering contents resolver by inheriting from theĀ Sitecore.LayoutService.ItemRendering.ContentsResolvers.RenderingContentsResolver class.

Make sure to install theĀ Sitecore.LayoutService Nuget package to your project from Sitecoreā€™s MyGet feed.

The code below recursively iterates through items, serializing their children to the ā€˜itemsā€™ property. Note the use of the base classĀ ProcessItem and ProcessItems methods, which ensures the Experience Editor support.

Note:Ā since the below code recursively iterates through itemā€™s .ChildrenĀ properly, this can lead to performance issues on large item trees (in my scenario the tree structures are rather small, so it is not an problem). Therefore, depending on your situation, you might want to optimize the code.Ā For example, the recursion can be replaced with a single call of the .GetDescendants() method, which would result in better performance. Also make sure to use HTML caching of renderings on top of that.

Step 2: create a resolver Sitecore item

To be able to use the resolver in your renderings, you need to create a Rendering Contents Resolver item underĀ /sitecore/system/Modules/Layout Service/Rendering Contents Resolvers. For example:

Custom rendering contents resolver item in Sitecore

Step 3: use the custom resolver in your rendering

Update the Rendering Contents ResolverĀ field in the Layout ServiceĀ section of your JSS Json Rendering.Ā 

Json rendering configuration

Alternative solutions

As an alternative to a custom Rendering Contents Resolver,Ā  you could use the Sitecore GraphQL API to shape the JSON output for your rendering and include datasource item children. However, this would not work out of the box with arbitrary item hierarchies, since GraphQL does not support "recursive" queries, which is well described here: Dealing with arbitrary hierarchies .

Nevertheless, it is still a valid solution in case you have a fixed amount of levels of item children.Ā 

For the menu items example with 3 levels of items, the GraphQL query (for the Integrated GraphQL ) might look like:

{
  dsItem: item(path: $datasource) {
    id
    name
    children {
      ... on MenuItem {
        name
        link {
          value
        }
        children {
          ... on MenuItem {
            name
            link {
              value
            }
            children {
              ... on MenuItem {
                name
                link {
                  value
                }
              }
            }
          }
        }
      }
    }
  }
}

Summary

There are multiple ways to deal with hierarchical item structures in JSS. As usual, the concrete approach depends on the use case.

  1. If item hierarchy is arbitrary, then you can go for a custom Rendering Contents Resolver. It will work for any rendering and data structure. This also means that the resolver can be reused in multiple renderings.
  2. In case amount of item levels in hierarchy is known in advance it might be enough to use GraphQL query to shape the output.
    • However, if you use Connected Mode take into account that this will result into additional queries to Sitecoreā€™s GraphQL endpoint.
    • If you use Integrated Mode, there is no additional calls made and data is shaped within the Layout Service output**.**
    • This approach is not reusable, meaning you will have to build specific queries for each component.