0

I have a json response coming from a service that's a bit of a pain to work with. How can I go about flattening this structure a bit so that "not_restricted":{"doc_count": 976465} is changed to "not_restricted":976465? I'd perfer a function that can detect this kind of a structure in all types of json docs and modify the result.

This is a the general structure of the json:

{
    "took": 159,
    "timed_out": false,
    "_shards": {
        "total": 6,
        "successful": 6,
        "failed": 0
    },
    "hits": {
        "total": 4909332,
        "max_score": 1,
        "hits": [
            {
                ...
            },
            {
                ...
            },
            {
                ...
            }
        ]
    },
    "aggregations": {
        "index_types": {
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 0,
            "buckets": [
                {
                    "key": "client",
                    "doc_count": 1958205,
                    "not_restricted": {
                        "doc_count": 976465
                    },
                    "restricted": {
                        "doc_count": 981740
                    }
                },
                {
                    "key": "ultimateparent",
                    "doc_count": 1616164,
                    "not_restricted": {
                        "doc_count": 741059
                    },
                    "restricted": {
                        "doc_count": 875105
                    }
                },
                {
                    "key": "facility",
                    "doc_count": 1334963,
                    "not_restricted": {
                        "doc_count": 914090
                    },
                    "restricted": {
                        "doc_count": 420872
                    }
                }
            ]
        }
    }
}
Horse Voice
  • 8,138
  • 15
  • 69
  • 120

4 Answers4

5

You can very easily do this by using the reviver parameter of JSON.parse, which allows you to filter every object in the JSON:

var res = JSON.parse(json, function(k, o) {
    if (Object(o) !== o) return o; // primitive values
    var keys = Object.keys(o);
    if (keys.length == 1 && keys[0] == "doc_count")
        return o.doc_count;
    else
        return o;
});

You can also test k to include "restricted" or so if you want to make sure that doc_count singletons are only replaced in such properties.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Bergi -I'm not sure what this does as I'm new to javascript, but does this modify the existing doc? Lets say `json` argument is the response doc. Does it just modify it? or is this just returning the aggs portion of it? – Horse Voice Oct 19 '15 at 21:25
  • No, it returns a parsed version (object) of the `json` string in which each `{"doc_count":…}` object is replaced by the `…` only. Btw, you cannot "modify" strings in JS, they're immutable. If you need to get JSON back, you can use `JSON.stringify(res, null, 4)`; and if you want you can overwrite the `json` variable with it. – Bergi Oct 19 '15 at 21:27
  • So it returns the entire doc? just with the slight modification of doc_count? – Horse Voice Oct 19 '15 at 21:28
  • 1
    Yes, it returns an object for the complete JSON without the doc_count objects. Just try it out and inspect the result in your console :-) – Bergi Oct 19 '15 at 21:28
  • How would I make this as a predefined method? I'm using extjs framework and it is not understanding this functional way of doing this. How can I make it so that I define a specific method that does just this, and then invoke it where I need it in the code, by passing in the response and this method returns the response? Also I don't think JSON.parse is working in my IE 8 old enterprise . – Horse Voice Oct 20 '15 at 14:57
  • Yes, you should just be able to wrap it in a function that takes `json` as a parameter and returns `res`. Not sure how your extjs code looks like and how to integrate it there, can you add that to your question? Yes, `JSON.parse` needs to be shimmed for IE8 support, but that should not be a problem. – Bergi Oct 20 '15 at 15:13
  • Okay this does not seem to work. It is returning an empty string. There are other fields on the json doc that are perhaps getting in the way. But this is a very specific alteration I need to make regarding `restricted` and `unrestricted` I updated the json in my question for the completeness of the doc structure. I need the entire doc back as is but with the specif modification. – Horse Voice Oct 20 '15 at 15:30
  • Oops, sorry for not testing (and forgetting what parameters the reviver takes :-/). Fixed snippet, tested against question, updated answer. – Bergi Oct 20 '15 at 15:42
  • That's why you are awesome! Thank you it works like a charm. – Horse Voice Oct 20 '15 at 17:41
1

You can use LoDash to make your life easier. Include it, and then you can use _.each

_.each(data.aggregations.index_types.buckets, function(n){
  n.not_restricted = n.not_restricted.doc_count;
  n.restricted = n.restricted.doc_count;
})
Eric Hartford
  • 16,464
  • 4
  • 33
  • 50
0

If you want to go thru the object and flatten all 1 property objects, you can do:

for (var i = 0; i < data.aggregations["index_types"].buckets.length; i++) {
    var obj = data.aggregations["index_types"].buckets[i];
    for (var key in obj) {
        if (typeof obj[key] === "object" && Object.keys(obj[key]).length === 1) {
            //1 property object!
            obj[key] = obj[key][Object.keys(obj[key])[0]];
        } 
    }
}

Demo: http://jsfiddle.net/jkm1vmbm/

tymeJV
  • 103,943
  • 14
  • 161
  • 157
  • It's actually a json string. Not really an object. Would It work the same way? sorry I'm new to javascript – Horse Voice Oct 19 '15 at 21:11
  • @HorseVoice -- Use `JSON.parse(str)` to convert it to an object - it won't work the same if it's just a string. – tymeJV Oct 19 '15 at 21:12
  • then I'd have to convert that back to a string for processing. for that I can just do String(jsonDoc) right? – Horse Voice Oct 19 '15 at 21:15
  • @HorseVoice -- `JSON.stringify(obj)` – tymeJV Oct 19 '15 at 21:17
  • This only returns the modified object and forgets the rest of the json. This does not work for me. I need the entire doc back with the flattened doc_count garbage inside restricted and unrestricted – Horse Voice Oct 20 '15 at 15:15
0

Since you said that the JSON are actually strings, if all you need is that specific replacement:

Replacing `{"doc_count": NUMBER}` with that `NUMBER`

What you can do is just use a regex to replace what you want:

s.replace(/({"doc_count":([0-9]+)})/g, '$2')

Which works like:

(                   // start capturing group 1
{\"doc_count\":     // match the string {\"doc_count\":
    (               // start capturing group 2
    [0-9]+          // match one or more digits
    )               // close capturing group 2
}                   // add the rest of the string (a closing bracket)  

Then you can use $2 in your replace() to extract only the number from the capturing group 2.

var json = {
  "aggregations": {
    "index_types": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [{
        "key": "client",
        "doc_count": 1958205,
        "not_restricted": {
          "doc_count": 976465
        },
        "restricted": {
          "doc_count": 981740
        }
      }, {
        "key": "ultimateparent",
        "doc_count": 1616164,
        "not_restricted": {
          "doc_count": 741059
        },
        "restricted": {
          "doc_count": 875105
        }
      }, {
        "key": "facility",
        "doc_count": 1334963,
        "not_restricted": {
          "doc_count": 914090
        },
        "restricted": {
          "doc_count": 420872
        }
      }]
    }
  }
};

var s = JSON.stringify(json);
// replace all instances of {doc_count: NUMBER} with that NUMBER
console.log(s.replace(/({"doc_count":([0-9]+)})/g, '$2'));

I also noticed that you have properties doc_count outside of restricted and non_restricted. If you only want to replace the ones within a property restricted or non_restricted, You can use this regex:

("((?:not_)?restricted"):{"doc_count":([0-9]+)})

That added part at the beginning basically means "starts with 'restricted' with an optional 'not_' before it"

It's used like:

s.replace(/("((?:not_)?restricted"):{"doc_count":([0-9]+)})/g, '"$2:$3')

var json = {
  "aggregations": {
    "index_types": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [{
        "key": "client",
        "doc_count": 1958205,
        "not_restricted": {
          "doc_count": 976465
        },
        "restricted": {
          "doc_count": 981740
        }
      }, {
        "key": "ultimateparent",
        "doc_count": 1616164,
        "not_restricted": {
          "doc_count": 741059
        },
        "restricted": {
          "doc_count": 875105
        }
      }, {
        "key": "facility",
        "doc_count": 1334963,
        "not_restricted": {
          "doc_count": 914090
        },
        "restricted": {
          "doc_count": 420872
        }
      }]
    }
  }
};

var s = JSON.stringify(json);
console.log(s.replace(/("((?:not_)?restricted"):{"doc_count":([0-9]+)})/g, '"$2:$3'));

NOTE: using ?: for the (?:not_) makes the part in parenthesis a non-capturing group so it doesn't get counted in the capturing groups. The term "restricted" with the optional "not_" before it is now contained in group 2 and the number value is in group 3. We also need to prepend a " and combine the two groups with a :.

nem035
  • 34,790
  • 6
  • 87
  • 99
  • It is a [regex capturing group](http://stackoverflow.com/a/6419003/3928341). Essentially, you can capture portions (or groups) of the matches. – nem035 Oct 19 '15 at 21:22
  • @HorseVoice I added much more explanation, hopefully that helps – nem035 Oct 19 '15 at 21:32
  • Yea, all the other structure MUST be same. Only restricted and unrestricted. There are other fields on the doc and lists etc, with nested doc_counts that I DONT want to mess with at all. This is very specific. How do I use this? `("(?:not_)?restricted":{"doc_count":([0-9]+)})` – Horse Voice Oct 20 '15 at 15:27
  • The same way you would use the other regex. I edited my answer accordingly. – nem035 Oct 20 '15 at 15:29
  • Okay, this did not do anything. The response was unmodified. And I still see doc_count inside of restricted and not_restricted – Horse Voice Oct 20 '15 at 15:39
  • @HorseVoice I found a bug, the regex I had was replacing the whole match with the number. Now I added a fix to also include the "restricted"/"not_restricted" portion and I added code snippet to demonstrate. – nem035 Oct 20 '15 at 15:42
  • @HorseVoice how was the response unmodified? Are you sure you are using it right? Using `.replace` creates a new string so you should use it like `var myNewJson = myOldJson.replace(...)` – nem035 Oct 20 '15 at 16:10
  • yes I'm doing it like that. I love this answer but since it didn't work I couldn't accept it. – Horse Voice Oct 20 '15 at 17:42
  • @HorseVoice You must not be using it correctly because it works in the code snippet I provided. If you run the script it will alert the JSON with the values altered as specified. – nem035 Oct 20 '15 at 17:43