1

This Meteor client app "mostly smartPhone usage" needs to accept input from user in the form of typed text and filter json data of about 500 bottom nodes, giving back the branches of the tree where the bottom node text contains the user input text.

{
  "people": {
    "honour": [
      [
        "family"
      ],
      [
        "friends"
      ]
    ],
    "respect": [
      [
        "one another"
      ]
    ]
  },
  "animals": {
    "eat": [
      [
        "row food"
      ]
    ]
  }
}

When the user inputs 'a', the code needs to give the tree where the occurrence exists:

people, honour, family.
people, respect, one another

When the user types 'o', output should be:
people, respect, one another.
animals, eat, row food.

When the user types 'oo', output should be:
animals, eat, row food.

when the user types 'f', output should be:
people, honour, family.
people, honour, friends.
animals, eat, row food.

My options are:

  1. Converting the json to javascript object and write the seach/find/match logic with few loops.
  2. Use defiantjs which I never used before and have to learn.
  3. Import the json to mongodb and filter the database.
  4. Whatever else you suggest.

Which would be best fit for fast results and easy of maintenance? Thanks

Fred J.
  • 5,759
  • 10
  • 57
  • 106
  • How many keys can this object contain? A few dozen, hundred, thousands? – user01 Jun 21 '16 at 02:55
  • Opps, nevermind. Missed the 500 nodes line. – user01 Jun 21 '16 at 03:03
  • Why don't you reverse the structure of the JSON file? Put the last search terms first, then you only have to search the first layer instead of recursively going three layers deep. It might yield a larger files with more repetition but will improve performance. (I can't see your current implementation being performant whichever way you pick) – d4nyll Jun 21 '16 at 03:10
  • Under `honour` you've got an arrays, each with one item. Is this how it's supposed to be our should it be just be a single array of strings? If it's the former, how is it supposed to behave when there's more than one item in the leaf node? – Dave Jun 21 '16 at 03:24
  • @dave Yes. that is right, there is arrays and they my have different number of inputs. I will put another example to answer your second question. – Fred J. Jun 21 '16 at 03:27
  • I'm still confused to why it's `[["family"],["friends"]]` and not just `["family","friends"]` but that's really no big deal. See my answer below and let me know if that works for you. – Dave Jun 21 '16 at 04:24
  • @dave There is much more to the data than the simplified example presented. :) – Fred J. Jun 21 '16 at 04:31

3 Answers3

1

OK this question was an excuse for me to create a generic Object method Object.prototype.paths() to get all the paths within an object. In objects there are values many paths. Some values might be the same at the end of different paths. We will generate an object with the original object's values as properties and these properties' values are going to be the paths. Each value might have several paths so an array of strings array where each strings array will contain a single path to that value.

So once we have this tool to map the object values and paths, it becomes very easy to get your result.

Object.prototype.paths = function(root = [], result = {}) {
  var ok = Object.keys(this);
  return ok.reduce((res,key) => { var path = root.concat(key);
                                  typeof this[key] === "object" &&
                                         this[key] !== null ? this[key].paths(path,res)
                                                            : res[this[key]] == 0 || res[this[key]] ? res[this[key]].push(path)
                                                                                                    : res[this[key]] = [path];
                                  return res;
                                },result);
};

var data = {"people":{"honour":[["family"],["friends"]],"respect":[["one another"],["friends"]]},"animals":{"eat":[["row food"]]}},
   paths = data.paths(),
  values = Object.keys(paths),
  keystr = document.getElementById("keystr");
getPaths = function(str){
 var valuesOfInterest = values.filter(f => f.includes(str));
 return valuesOfInterest.reduce((p,c) => p.concat({[c]: paths[c]}),[]);
};
keystr.oninput = function(e){
                   console.log(JSON.stringify(getPaths(e.target.value),null,2))
                 }
<input id="keystr" placeholder = "enter some characters" value = ""/>

So when you press "o" you will get the following

[
  {
    "one another": [
      [
        "people",
        "respect",
        "0",
        "0"
      ]
    ]
  },
  {
    "row food": [
      [
        "animals",
        "eat",
        "0",
        "0"
      ]
    ]
  }
]

Which means:

  • The outer array has 2 object items. This means that in the original object there are two values with "o" character/s in it. "one another" and "row food",
  • "one another" has only one path ["people", "respect", "0", "0"]. If "one another" was listed at multiple places like "friends" listed both under "respect" and "honour" then this array would contain two sub arrays with paths. Type "fr" and see it for yourself.

A few words of warning: We should be cautious when playing with the Object prototype. Our modification should have the descriptor enumerable = false or it will list in the for in loops and for instance jQuery will not work. (this is how silly jQuery is, since apparently they are not making a hasOwnProperty check in their for in loops) Some good reads are here and here So we have to add this Object method with Object.defineProperty() to make it enumerable = false;. But for the sake of simplicity and to stay in the scope of the question i haven't included that part in the code.

Community
  • 1
  • 1
Redu
  • 25,060
  • 6
  • 56
  • 76
  • @Fred J. OK now it allows you type letters and will immediately return you the relevant objects. – Redu Jun 21 '16 at 08:57
0

I think a few loops would work just fine. In the example below, as you type in the input, the results matching your search are logged to the console.

$("#search").on("input", function() {
  var result = [];
  var search = this.value;

  if (search.length) {
    $.each(data, function(key1, value1) {
      //key1: people, animals
      $.each(value1, function(key2, value2) {
        //key2: honor, respect, eat
        $.each(value2, function(i, leaf) {
          if (leaf.length && leaf[0].indexOf(search) >= 0) {
            //found a match, push it onto the result
            var obj = {};
            obj[key1] = {};
            obj[key1][key2] = leaf;
            result.push(obj);
          }
        });
      });
    });
  }

  console.log(result);
});

var data = {
  "people": {
    "honour": [
      [
        "family"
      ],
      [
        "friends"
      ]
    ],
    "respect": [
      [
        "one another"
      ]
    ]
  },
  "animals": {
    "eat": [
      [
        "row food"
      ]
    ]
  }
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input id="search" />
Dave
  • 10,748
  • 3
  • 43
  • 54
0

Use this package for meteor is awesome https://atmospherejs.com/matteodem/easy-search