4
{
"groups": [
    {
        "name": "Event",
        "groups": [
            {
                "name": "Service",
                "subscriptions": [
                    {
                        "topic": "SERVICE_STATUS_PRESETS"
                    },
                    {
                        "topic": "AIRCRAFT_ACTIVATION",

                    },
                    {
                        "topic": "OUT_OF_SERVICE",

                    }
                ]
            }
        ]
    },
    {
        "name": "Enquiries",
        "groups": [
            {
                "name": "Service-related",
                "subscriptions": [
                    {

                        "topic": "PROMO_CODES_REQUESTS",

                    }
                ]
            }
        ]
    }
],
"subscriptions": [
    {
        "topic": "BANNERS",
    },
    {
        "topic": "DOCUMENTS",
    },
    {
        "topic": "USER",
    }
]

}

OK guys I have such JSON structure what I need is to: return all topics in array, in this example it will be:

["SERVICE_STATUS_PRESETS", "AIRCRAFT_ACTIVATION", "OUT_OF_SERVICE", "PROMO_CODES_REQUESTS", "BANNERS", "DOCUMENTS", "USER"]

I try recursive calls like this, though I only get last three records:

getRecursive() {
if (Array.isArray(data)) {
       for (let i = 0; i < data.length; i++) {
         if (data[i].subscriptions) {
           return data[i].subscriptions.map((val: SubscriptionGroupDetails) => val.topic);
         } else if (data[i].groups) {
           return this.getAllTopics(data[i].groups);
         }
       }
     }
     if (data && data.groups) {
      return this.getAllTopics(data.groups);
     }
     return data.subscriptions.map((val: SubscriptionGroupDetails) => val.topic);
}
3
  • stackoverflow.com/questions/11922383/… Commented Feb 14, 2020 at 15:23
  • @Teemu: that doesn't look particularly relevant. Commented Feb 14, 2020 at 15:48
  • 1
    @ScottSauyet Maybe not, but there's no recursion (at least obvious) in the provided example, maybe the answers in the linked post give some ideas. Commented Feb 14, 2020 at 15:53

5 Answers 5

5

You could take a recursive approach and check

  • if the handed over data is not an object for checking, then return with empty array,
  • if the object has the wanted property, then return an array with the value of topic,
  • or get the values and make a recursive call with the function and return an array with the result of it.

function getTopics(object) {
    if (!object || typeof object !== 'object') return [];
    if ('topic' in object) return [object.topic];
    return Object.values(object).reduce((r, v) => [...r, ...getTopics(v)], []);
}

var data = { groups: [{ name: "Event", groups: [{ name: "Service", subscriptions: [{ topic: "SERVICE_STATUS_PRESETS" }, { topic: "AIRCRAFT_ACTIVATION" }, { topic: "OUT_OF_SERVICE" }] }] }, { name: "Enquiries", groups: [{ name: "Service-related", subscriptions: [{ topic: "PROMO_CODES_REQUESTS" }] }] }], subscriptions: [{ topic: "BANNERS" }, { topic: "DOCUMENTS" }, { topic: "USER" }] },
    result = getTopics(data);

console.log(result);

Sign up to request clarification or add additional context in comments.

Comments

2

Here is a solution using object-scan

// const objectScan = require('object-scan');

const data = {"groups":[{"name":"Event","groups":[{"name":"Service","subscriptions":[{"topic":"SERVICE_STATUS_PRESETS"},{"topic":"AIRCRAFT_ACTIVATION"},{"topic":"OUT_OF_SERVICE"}]}]},{"name":"Enquiries","groups":[{"name":"Service-related","subscriptions":[{"topic":"PROMO_CODES_REQUESTS"}]}]}],"subscriptions":[{"topic":"BANNERS"},{"topic":"DOCUMENTS"},{"topic":"USER"}]};

const searchTopics = (obj) => objectScan(['**.topic'], { rtn: 'value' })(obj);

console.log(searchTopics(data));
/* => [
  'USER',
  'DOCUMENTS',
  'BANNERS',
  'PROMO_CODES_REQUESTS',
  'OUT_OF_SERVICE',
  'AIRCRAFT_ACTIVATION',
  'SERVICE_STATUS_PRESETS'
] */
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Comments

1

use this:

function getTopics(obj){
   if(typeof obj !== 'object') return [];
   if(obj.topic) return [obj.topic];
   var res = [];
   for(var i in obj){
     res.push(...getTopics(obj[i]));
   }
   return res;
}

Working example:

const topics = {
"groups": [
    {
        "name": "Event",
        "groups": [
            {
                "name": "Service",
                "subscriptions": [
                    {
                        "topic": "SERVICE_STATUS_PRESETS"
                    },
                    {
                        "topic": "AIRCRAFT_ACTIVATION",

                    },
                    {
                        "topic": "OUT_OF_SERVICE",

                    }
                ]
            }
        ]
    },
    {
        "name": "Enquiries",
        "groups": [
            {
                "name": "Service-related",
                "subscriptions": [
                    {

                        "topic": "PROMO_CODES_REQUESTS",

                    }
                ]
            }
        ]
    }
],
"subscriptions": [
    {
        "topic": "BANNERS",
    },
    {
        "topic": "DOCUMENTS",
    },
    {
        "topic": "USER",
    }
]
}

function getTopics(obj){
  if(typeof obj !== 'object') return [];
  if(obj.topic) return [obj.topic];
  var res = [];
  for(var i in obj){
    res.push(...getTopics(obj[i]));
  }
  return res;
}


console.log(getTopics(topics));

Comments

1

This version that takes a functional approach if you are interested. There are others above but this is just another way to look at it.

const recursion = object => Object.entries(object).map(([a, b]) => {
  if (a === 'topic') return b;
  if (Array.isArray(b)) return b.map(recursion);
  return [];
}).flat(Infinity);

recursion(obj);

1 Comment

Trade out map() for flatMap() (and remove .flat(Infinity) in this function for a slight performance boost: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
0

EDIT: Added another way of doing it with .reduce()

You can create an empty array of topics and then navigate through your nested structure recursively, adding a topic each time you come across it, using the javascript .forEach() to cycle through any nested groups or subscriptions.

let topics = [];

let findTopics = obj => {
   if (obj.groups) {
      obj.groups.forEach(findTopics);
   }
   if (obj.subscriptions) {
      obj.subscriptions.forEach(findTopics);
   }
   if (obj.topic) {
      topics.push(obj.topic);
   }
}

findTopics(data);

Or a possibly neater way using .reduce():

let findTopicsRecursive = (topics, obj) => {
   if (obj.groups) {
      topics = obj.groups.reduce(findTopicsRecursive, topics);
   }
   if (obj.subscriptions) {
      topics = obj.subscriptions.reduce(findTopicsRecursive, topics);
   }
   if (obj.topic) {
      topics.push(obj.topic);
   }
   return topics;
}

let findTopics = data => findTopicsRecursive([], data);

let topics = findTopics(data);

4 Comments

@YosefTukachinsky you mean to say that this code is very dependent on a specific data structure?
Yes this what I mean indeed
@calarin: See some of the other answers for techniques that do this more generically.
@scottsauyet YosefTukachinsky Yes good point - I see what you mean!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.