How to get deeply nested object structure from array of values with Javascript


I have JSON representing some content in a text editor. Here's a sample:

{ "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "bold" } ], "text": "Testing" }, { "type": "text", "text": " " }, { "type": "text", "marks": [ { "type": "strike" } ], "text": "Testing " }, { "type": "text", "marks": [ { "type": "strike" }, { "type": "underline" } ], "text": "Testing" }, { "type": "text", "marks": [ { "type": "strike" } ], "text": " Testing" } ] }, { "type": "paragraph" }, { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "bold" } ], "text": "Testing " }, { "type": "text", "marks": [ { "type": "bold" }, { "type": "italic" }, { "type": "strike" } ], "text": "Testing" }, { "type": "text", "marks": [ { "type": "bold" } ], "text": " Testing" } ] }, { "type": "paragraph" } ] }

It's good because I can safely turn it into html, however instead of having nested values for styles, it represents this styling in a marks sub-array. What I would prefer if to store the data already nested with respect to it's natural hierarchy.

So instead of:

{ 
     "type":"text",
     "marks": [{ "type": "bold" }, { "type": "italic" }],
     "text": "Testing"
}

I'd like to have something that more accurately represents the resulting HTML:

{
  "type" : "bold", 
   "content" : [{ 
       "type" : "italic", 
       "content":[{ 
            "type" : "text", 
            "text" : "Testing"
       }]
   }]
}

I've tried various ways of parsing the json and storing it like below, but I think I'm missing somethign fundamental about how javascript works with objects, because the changes aren't being made.

I've done various iteration of the function below, over each marks array in the json.

item = {type:type, text:text}

marks.forEach((mark)=>{
   let newObj = {content:[], type:mark.type}
    item = newObj.content.push(item)
});

Some extra background information, this is the json generated by the tiptap wysiwyg editor, which is based on prosemirror. I can't use the normal html export because I'm doing special stuff to the data. Any help would be greatly appreciated.

EDIT : Dacre Denny came up with an answer using reduceRight() which answered the first part of the problem. However, the function below still returns the original object unchanged.

   const content = editorContent.content

    function parseContent (arr) {
      arr.forEach((item)=>{

        // Check if item contains another array of items, and then iterate
        over that one too.

        if(item.content){
          parseContent(item.content)
        }

        if(item.marks){
          //Extract values from item

          // THis is the correct function
          const processContent = ({ marks, text }) =>
          marks.reduceRight((acc, mark) => ({ type: mark.type, content: [acc]
            }), {
              type: "text",
              text: text
            });

          item = processContent({ text:item.text, marks:item.marks })


        }
      })
    }

    parseContent(content)

return content
//

One possiblity would be to use reduceRight to "reduce" the marks sub-array of your input content to a single object with the required "nested structure".

The idea is to start the reduction with the { type : "text", text : content.text } object (that will be nested), and then incrementally "wrap" that object with objects that contain the "type" data.

To achieve the correct order of "wrapping" for objects with the type metadata, the marks array needs to be reduced in reverse. This is why reduceRight is used (rather than regular reduce).

Here's an example snippet to show this idea in action:

/* Helper function processes the content object, returns the required
nested structure */
const processContent = ({ marks, text }) => 
    marks.reduceRight((acc, mark) => ({ type: mark.type, content: [acc] 
}), {
  type: "text",
  text: text
});

console.log(processContent({
  "type": "text",
  "marks": [{
    "type": "bold"
  }, {
    "type": "italic"
  }],
  "text": "Testing"
}))

/*
Result:
{
  "type" : "bold", 
   "content" : [{ 
       "type" : "italic", 
       "content":[{ 
            "type" : "text", 
            "text" : "Testing"
       }]
   }]
}
*/

Hope that helps!