skip to Main Content

There is a nested object array and I have to get all unique refIds, which are existing at any level.

I tried to iterate with for-loops, but it really gets very complicated. And also I don’t know how deep the data exactly is.
But is always an "type": "text"element with an optional "marks" field.

[
  {
    "type": "bulletList",
    "content": [
      {
        "type": "listItem",
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "marks": [ // optional mark
                  { "type": "refId", "attrs": { "refIds": [123, 234] } } // need this values
                ],
                "text": "Item 1"
              }
            ]
          }
        ]
      },
      {
        "type": "listItem",
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "marks": [{ "type": "refId", "attrs": { "refIds": [987] } }],
                "text": "Item 2"
              }
            ]
          }
        ]
      },
      {
        "type": "listItem",
        "content": [
          {
            "type": "paragraph",
            "content": [{ "type": "text", "text": "Item 3" }] // has no mark
          },
          {
            "type": "bulletList", // example for nested child
            "content": [
              {
                "type": "listItem",
                "content": [
                  {
                    "type": "paragraph",
                    "content": [
                      {
                        "type": "text",
                        "marks": [
                          { "type": "refId", "attrs": { "refIds": [876] } }
                        ],
                        "text": "Sub 1"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

Output should be

["123", "234", "987"]

3

Answers


  1. Just walk recursively for arrays and objects and collect the ids into a set:

    const walk = (arr, out = new Set) => {
      if(Array.isArray(arr)){
        arr.forEach(r => walk(r, out));
      }else if(arr && typeof arr ==='object'){
        for(const key in arr){
          if(key === 'refIds' && Array.isArray(arr[key])){
            arr[key].forEach(r => out.add(r));
          }else walk(arr[key], out);
        }
      }
      return out;
    
    }
    
    const result = [...walk(arr)];
    
    console.log(result);
    <script>
    const arr=[{type:"bulletList",content:[{type:"listItem",content:[{type:"paragraph",content:[{type:"text",marks:[{type:"refId",attrs:{refIds:[123,234]}}],text:"Item 1"}]}]},{type:"listItem",content:[{type:"paragraph",content:[{type:"text",marks:[{type:"refId",attrs:{refIds:[987]}}],text:"Item 2"}]}]},{type:"listItem",content:[{type:"paragraph",content:[{type:"text",text:"Item 3"}]},{type:"bulletList",content:[{type:"listItem",content:[{type:"paragraph",content:[{type:"text",marks:[{type:"refId",attrs:{refIds:[876]}}],text:"Sub 1"}]}]}]}]}]}];
    </script>
    Login or Signup to reply.
  2. function getAllIds(data) {
    const refIds = new Set();
    
    function iterate(el) {
        if (el.type === "text" && el.marks) {
            el.marks.forEach(mark => {
                if (mark.type === "refId" && mark.attrs && mark.attrs.refIds) {
                    mark.attrs.refIds.forEach(refId => refIds.add(refId));
                }
            });
        }
    
        if (el.content) {
            el.content.forEach(child => iterate(child));
        }
    }
    
    data.forEach(item => iterate(item));
    return [...refIds];
    

    }

    Login or Signup to reply.
  3. Assuming that missing 876 from the output was a mistake, you can use .flatMap() on your array. For each object that you map you can check if its type is text, and if it is, look for a marks array that you then perform a nested flatMap on to transform into an array of ids. Since this inner .flatMap() is done in a parent .flatMap() the inner array results will be flattened/concatenated into the resulting array that’s eventually returned by the parent .flatMap() (avoiding nested arrays). Otherwise, if the current object isn’t of type text, then you can perform a recursive call, passing in the content of the current object back into the getRefIds() function to get the refIds from that nested array (we default content to an empty array if it doesn’t exist, which gets ignored when eventually flattened/merged into the resulting array). Finally, you can convert the result into a Set and then back into an array to deduplicate:

    const arr = [ { "type": "bulletList", "content": [ { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "refId", "attrs": { "refIds": [123, 234] } } ], "text": "Item 1" } ] } ] }, { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [{ "type": "refId", "attrs": { "refIds": [987] } }], "text": "Item 2" } ] } ] }, { "type": "listItem", "content": [ { "type": "paragraph", "content": [{ "type": "text", "text": "Item 3" }]  }, { "type": "bulletList", "content": [ { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "refId", "attrs": { "refIds": [876] } } ], "text": "Sub 1" } ] } ] } ] } ] } ] } ];
    
    const getRefIds = (arr) => {
      return arr.flatMap(({type, content = [], marks = []}) => type === "text" 
        ? marks.flatMap(mark => mark.attrs.refIds) // add an additional `.filter(mark => mark.type === "refId")` if you can have other types in `marks`
        : getRefIds(content)
      );
    }
    
    const res = Array.from(new Set(getRefIds(arr)));
    console.log(res);

    If your data is large, then you might want to avoid creating intermediate arrays (as done in the above example with each recursive call by the .flatMaps), and so you can try and use a nested recursive function like below to help:

    const arr = [ { "type": "bulletList", "content": [ { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "refId", "attrs": { "refIds": [123, 234] } } ], "text": "Item 1" } ] } ] }, { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [{ "type": "refId", "attrs": { "refIds": [987] } }], "text": "Item 2" } ] } ] }, { "type": "listItem", "content": [ { "type": "paragraph", "content": [{ "type": "text", "text": "Item 3" }]  }, { "type": "bulletList", "content": [ { "type": "listItem", "content": [ { "type": "paragraph", "content": [ { "type": "text", "marks": [ { "type": "refId", "attrs": { "refIds": [876] } } ], "text": "Sub 1" } ] } ] } ] } ] } ] } ];
    
    const getRefIds = (arr) => {
      const refIds = new Set();
      const searchContent = (contentArr) => {
        for(const {type, content = [], marks = []} of contentArr) {
          if (type === "text")
            marks.forEach(mark => {
              if (mark.type === "refId") // again, add this if only if required.
                mark.attrs.refIds.forEach(id => refIds.add(id));
            });
          else
            searchContent(content);
        }
      } 
      searchContent(arr);
      return Array.from(refIds); // convert the set into an array
    }
    
    const res = getRefIds(arr);
    console.log(res);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search