Skip to content
Astera Technology

Build nested sections from open and close events

Use a stack to preserve document nesting and detect unmatched boundaries.

Download complete case notes and answer keys ↓

Source contract and expected records

This is an original, fully synthetic mapping example. Inputs are plain, acyclic JSON-shaped records with the fields shown; the canonical shape and rejection codes belong to this example. Adapt the contract before using a different source.

The source stream has open with a nonempty label, text with a string value, and close for the most recently opened section. Text can also appear at the root. Build section/text objects in source order. Reject a close with no open section or any section left open at the end; empty closed sections are preserved.

Editable synthetic mapping cases
CaseInput record (JSON)Expected output (JSON)
Build nested sections{"events":[{"kind":"open","label":"Outer"},{"kind":"open","label":"Inner"},{"kind":"text","value":"Note"},{"kind":"close"},{"kind":"close"}]}{"ok":true,"value":{"parts":[{"kind":"section","label":"Outer","parts":[{"kind":"section","label":"Inner","parts":[{"kind":"text","text":"Note"}]}]}]}}
Reject unmatched close{"events":[{"kind":"close"}]}{"ok":false,"error":"SECTION_CLOSE"}
Preserve empty section{"events":[{"kind":"open","label":"Empty"},{"kind":"close"}]}{"ok":true,"value":{"parts":[{"kind":"section","label":"Empty","parts":[]}]}}
Download editable CSV ↓

Run the local transformation

Run the complete example as a JavaScript module in Node.js. It contains the pure mapping function, all three inputs and literal expected outputs; assertions stop on a mismatch. A rejected case returns ok=false with an error code, rather than a partial canonical record.

Complete function and executable cases
import assert from 'node:assert/strict';

function nestedSections(i){
  const fail=error=>({ok:false,error});if(!i||!Array.isArray(i.events))return fail('SECTION_EVENTS');
  const parts=[],stack=[];
  for(const e of i.events){
    if(!e)return fail('SECTION_KIND');const destination=stack.length?stack.at(-1).parts:parts;
    if(e.kind==='open'){
      if(typeof e.label!=='string'||!e.label)return fail('SECTION_LABEL');
      const section={kind:'section',label:e.label,parts:[]};destination.push(section);stack.push(section);
    }else if(e.kind==='text'){
      if(typeof e.value!=='string')return fail('SECTION_TEXT');destination.push({kind:'text',text:e.value});
    }else if(e.kind==='close'){
      if(!stack.length)return fail('SECTION_CLOSE');stack.pop();
    }else return fail('SECTION_KIND');
  }
  if(stack.length)return fail('SECTION_UNCLOSED');return {ok:true,value:{parts}};
}

// Original synthetic cases; expected outputs are literal records.
const cases = [
  {
    "label": "Build nested sections",
    "input": {
      "events": [
        {
          "kind": "open",
          "label": "Outer"
        },
        {
          "kind": "open",
          "label": "Inner"
        },
        {
          "kind": "text",
          "value": "Note"
        },
        {
          "kind": "close"
        },
        {
          "kind": "close"
        }
      ]
    },
    "expected": {
      "ok": true,
      "value": {
        "parts": [
          {
            "kind": "section",
            "label": "Outer",
            "parts": [
              {
                "kind": "section",
                "label": "Inner",
                "parts": [
                  {
                    "kind": "text",
                    "text": "Note"
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  },
  {
    "label": "Reject unmatched close",
    "input": {
      "events": [
        {
          "kind": "close"
        }
      ]
    },
    "expected": {
      "ok": false,
      "error": "SECTION_CLOSE"
    }
  },
  {
    "label": "Preserve empty section",
    "input": {
      "events": [
        {
          "kind": "open",
          "label": "Empty"
        },
        {
          "kind": "close"
        }
      ]
    },
    "expected": {
      "ok": true,
      "value": {
        "parts": [
          {
            "kind": "section",
            "label": "Empty",
            "parts": []
          }
        ]
      }
    }
  }
];
for (const sample of cases) {
  const actual = nestedSections(sample.input);
  assert.deepEqual(actual, sample.expected);
  console.log(sample.label, JSON.stringify(actual));
}
Download runnable example ↓

Work through the boundary

Where does Note belong after Outer then Inner are opened?

Show the worked answer

Note belongs inside Inner, which belongs inside Outer. Each close exits one level. The empty-section case retains an Empty section with parts=[] rather than deleting it.

Related resources