Search code examples
javascriptjsonparsingindentation

How do I parse the indentation level of a string into a JSON Object?


I'd like to be able to parse a string into a JSON Object, something like this (the text can be anything, I'm just putting them like this so you can see the structure):

A
  A-A
  A-B
    A-B-A
    A-B-B
  A-C
    A-C-A
B

into a json object, structured like this:

[
  {
    "root": "A",
    "content": [
      { "root": "A-A", "content": [] },
      {
        "root": "A-B",
        "content": [
          { "root": "A-B-A", "content": [] },
          { "root": "A-B-B", "content": [] }
        ]
      },
      {
        "root": "A-C",
        "content": [
          { "root": "A-C-A", "content": [] }
        ]
      }
    ]
  },
  { "root": "B", "content": [] }
]

So far, I have the following, but I'm not sure if this is the best way of doing it. Maybe a recursive approach would be better?

  let body = [];
  let indentStack = [0];
  for (let line of input.split('\n')) { // input is the string I'd like to parse
    if (line.trim() == '') continue; // skips over empty lines
    let indent = line.match(/^ +/);
    indent = indent ? indent[0].length : 0; // matches the first group of spaces with regex, gets the indent level of this line
    if (indentStack[indentStack.length-1] != indent) 
      if (indentStack.includes(indent)) indentStack.length = indentStack.indexOf(indent)+1; // remove all indent levels after it as it's returned back to a higher level
      else stack.push(indent);
    console.log(`${(indent + '[' + indentStack.join() + ']').padEnd(10, ' ')}: ${line}`); // debugging
      
    if (indentStack.length == 1) body.push({ root: line, content: [] });
    else {
      body[body.length-1].content.push({ root: line.substring(indent), content: [] })
    }
  }
  console.log(body)

Solution

  • I will do that this way :

    const data =
    `A
      A-A
      A-B
        A-B-A
        A-B-B
      A-C
        A-C-A
    B`;
    
    
    function doTree(data)
      {
      let
        res    = []
      , levels = [ res ]
        ;
      for (let line of data.split('\n'))  
        {      
        let 
          level   = line.search(/\S/) >> 1  // (index of first non whitespace char) / 2 --> IF indentation is 2 spaces
        , root    = line.trim()
        , content = []
          ;
        if (!root) continue
        levels[level].push({root,content})
        levels[++level] = content
        }
      return res
      }
    
    console.log( doTree(data) )
    .as-console-wrapper {max-height: 100%!important;top:0 }

    The question about indentations ...

    here you can have unequal indentation steps,
    either with spaces or with tabs.
    (do not mix spaces and tabs)

    const data_023c = // indentation values are 0c, 2c, 3c
    `A
      A-A
      A-B
         A-B-A
         A-B-B
      A-C
         A-C-A
    B`;
    
    const indentation= (()=>  // IIFE 
      {
      let 
        indents = []
      , max     = -1
        ;
      return {
        clear:() => 
          {
          indents.length = 0
          max  = -1
          }
      , get:(line, lNum='?' ) =>
          {
          let ncBefore = line.search(/\S/)
    
          let level = indents.indexOf(ncBefore)
          if (level===-1)
            {
            if (ncBefore < max) throw `error on indentation,\n line = ${lNum},\n line value is = "${line}"`
            level = indents.push( ncBefore) -1
            max   = ncBefore
            }
          return level
          }
        }
      })()
    
    const doTree = data =>
      {
      let
        res    = []
      , levels = [ res ]
      , lineN  = 0
        ;
      indentation.clear()
      for (let line of data.split('\n'))  
        {
        lineN++  // line counter for indent error message
        let
          root    = line.trim()
        , content = []
          ;
        if (!root) continue
        let level = indentation.get(line, lineN)
         
        levels[level].push({root,content})
        levels[++level] = content
        }
      return res
      }
    
    console.log( doTree(data_023c) )
    .as-console-wrapper {max-height: 100%!important;top:0 }