4

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
//

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
Nathaniel
  • 394
  • 1
  • 6
  • 14

1 Answers1

2

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!

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
  • Thanks, so much, that's definitely the right way of doing it! but, I'm still having trouble assinging the new value. `item = processContent` just leaves me with the same object I started with. – Nathaniel Oct 06 '19 at 21:31
  • You're welcome - it looks like you assigning the `processContent` function to `item`. If you change your code to `var item = processContent({ text:text, marks : marks })` does that work as expected? – Dacre Denny Oct 06 '19 at 21:35
  • It works great when I log it to the console, but then the object is still the same afterwards. I'm doing all this in a forEach loop that iterates over the whole object. So I'm assigning the values within the loop. It's fine with changing the string property of type but it doesn't work when I try to assign a new value to an object. – Nathaniel Oct 06 '19 at 21:55
  • would you be able to update question above, by adding a bit of the surrounding code your describing here? that should give me some more context so i can better help out :) – Dacre Denny Oct 06 '19 at 22:01
  • Sure thing, thanks again, I've updated the question, to include the function I'm using to update the object (or not update it) – Nathaniel Oct 06 '19 at 22:12
  • I see - you're relying on mutation of the input `arr` array. Here's a gist showing one solution for it to work as required https://gist.github.com/dacre-denny/f8fc1a04774fda04549781678b37a4a4 – Dacre Denny Oct 06 '19 at 22:15