9

I've looked around for an answer, but I think this is a kind of weird question. How would I convert, as a text file using tabs for spacing, this:

parent
    child
    child
parent
    child
        grandchild
        grandhcild

to

{
"name" : "parent",
"children" : [
    {"name" : "child"},
    {"name" : "child"},
]
},
{
"name" : "parent",
"children" : [
    {
    "name" : "child",
    "children" : [
        {"name" : "grandchild"},
        {"name" : "grandchild"},
        {"name" : "grandchild"},
    ]
    },
]
}

JSON probably isn't perfect, but hopefully makes my point clear.

JSuar
  • 21,056
  • 4
  • 39
  • 83
user1114864
  • 1,734
  • 6
  • 26
  • 37

4 Answers4

6

i've had the same problem. Here is the solution:

function node(title,lvl){
    var children = [],
        parent = null;
    return {
        title:title,
        children:children,
        lvl:()=>lvl==undefined?-1:lvl,
        parent:()=>parent, //as a function to prevent circular reference when parse to JSON
        setParent:p=>{parent=p},
        appendChildren: function(c){
            children.push(c); 
            c.setParent(this);
            return this
        },
    }
}
function append_rec(prev,curr) {
    if(typeof(curr)=='string'){ //in the recursive call it's a object
        curr = curr.split('    ');//or tab (\t)
        curr = node(curr.pop(),curr.length);
    }
    if(curr.lvl()>prev.lvl()){//curr is prev's child
        prev.appendChildren(curr);
    }else if(curr.lvl()<prev.lvl()){
        append_rec(prev.parent(),curr) //recursive call to find the right parent level
    }else{//curr is prev's sibling
        prev.parent().appendChildren(curr);
    }

    return curr;
}

root = node('root');

var txt = 
`parent
    child
    child
parent
    child
        grandchild
        grandhcild`;
        
txt.toString().split('\n').reduce(append_rec,root); 

console.log(JSON.stringify(root.children,null,3));
3

I've just implemented this feature for the tabdown markup language — it does exactly what you sought for. https://github.com/antyakushev/tabdown

Usage is pretty simple:

var lines = data.toString().split('\n');
var tree = tabdown.parse(lines);
console.log(tree.toString());

You can also use the parse function outside of node.js, it does not depend on any modules.

  • Oh, it's just a less verbose way to print out the same data. However, the tree structure itself does contain properties "data" and "children" (for "name" and "children" in the example respectively), therefore it can be used as a js object in the desired format. – Anton Yakushev Aug 07 '14 at 19:11
  • 1
    This looks like a good solution, but tabdown is not available on npm. – jeffld Feb 19 '20 at 20:28
0

This is my regex-based, recursion-free approach. It looks a bit "hacky" but makes perfect sense, you can try each step on regexr if you want to. It's written purposely verbose and can probably be compressed a bit. Also, this code assumes your text is tab-indented and only has one "parent", but you should be able to easily replace your indents and add a single "root" parent beforehand.

const string = `
parent
    child
        grandchild
    child
    child
        grandchild
        grandchild
`;



let json = string
  .replace(
    /(?:(\t+)(\S+)(?=(?:\n(?:(?:(?!\1))|(?:\1\S)))|$))/g,
    "$1{\n$1\t\"name\": \"$2\",\n$1\t\"children\": []\n$1},"
  ) // this one replaces all empty nodes with a simple object with an empty children array
  .replace(
    /(?<=(^\t*))([^\s{]+)$\n(?=\1\t)/gm,
    "{\"name\": \"$2\",\"children\": [\n"
  ); // this one replaces every immediate parent with an object and a starting children array

const lines = string.split("\n");

const maxDepth = Math.max(
  ...lines.map(line => line.replace(/[^\t]/g, "").length)
);

// this one basically closes all square brackets and curly braces
// this is a loop because it depends on the max depth of your source text and i also don't like recursion
for (let index = 0; index < maxDepth - 1; index++) {
  json = json.replace(
    /(^\t+)(.*,)("children": \[)((\n\1\t+[^\t\n]+)+)/gm,
    "$1$2\n$1$3$4\n$1]},"
  )
}

// this closes the root object brackets and removes trailing commas and newlines
json = `${json}\n]}`.replace(/,(?=\s*\])/g, "").replace(/\n/g, "");

const object = JSON.parse(json);

const betterLookingJson = JSON.stringify(object, null, "\t");

console.log(object);
console.log(betterLookingJson);
Nano Miratus
  • 467
  • 3
  • 13
-2

Generate JSON from Tab Tree Text File

The links below attack your problem specifically. All you need to do is update the code so the output is formatted to your requirements.


Tab Delimiter to JSON

Other Help

Community
  • 1
  • 1
JSuar
  • 21,056
  • 4
  • 39
  • 83
  • To clarify, this doesn't really have anything to do with a CSV or TSV file. Tabs are used purely to show the generational level in the tree. If a line is indented once, it's in the second generation. No tabs means it's first-generation, and two tabs is third-generation. – user1114864 Jan 09 '14 at 22:14
  • @user1114864 The links I added address your problem. – JSuar Jan 10 '14 at 01:39