Last active
January 6, 2020 05:23
-
-
Save drwpow/37d65f87501d05af4e1372511e0d32e4 to your computer and use it in GitHub Desktop.
Limiting circular JS object depth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Limit depth of any object by key name (ex: limitDepth(myObj, {foo: 3, bar: 5}) limits “foo” to 3 nested occurrences and “bar” to 5 nested occurrences) | |
* @param {any} obj | |
* @param {{[index:string]:number}} depth | |
*/ | |
function limitDepth(obj, depth) { | |
const count = {}; | |
return JSON.parse( | |
JSON.stringify(obj, (name, value) => { | |
if (typeof count[name] !== 'number') { | |
count[name] = 0; | |
} | |
count[name] += 1; | |
const max = depth[name] >= 0 ? depth[name] : Infinity; | |
if (count[name] <= max) { | |
if (typeof value === 'object') { | |
return Array.isArray(value) ? [...value] : { ...value }; | |
} | |
return value; | |
} | |
return undefined; | |
}) | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Limiting circular JS object depth
Function that allows you to take a circular JS object, and limit the depth based on the number of times a key appears. Useful when you want to expose a circular JS object via an API, or when saving a circular object to a NoSQL database.
Example
Drawbacks
There are 2 drawbacks of this approach.
The first is this assumes unique keys in your JSON structure. If you have to deal with duplicate keys, you may need to extend this function to suit your needs.
The second is you must manually specify all nested object keys. It’ll throw an error if some part of the structure is recursively nested and you don’t specify the max depth you’d like to reach.
Additional explanation (optional)
Read on only if you’d like to learn more about how this works.
What is a “circular structure?”
Let’s create 2 objects like so, and reference each from the other:
Let’s inspect the
director
object we just made withconsole.log(director);
:You get the idea—it just repeats and repeats and repeats and repeats…
This works if we stay within JavaScript because JavaScript (thankfully) is just re-using memory addresses at no additional cost. But say we wanted to serve this object via an API, or store it in a database. Let’s see what happens when we try
JSON.stringify(director)
:And here we have a circular structure—an object that references another object that references itself. It has a myriad of uses, but very tough to expose via an API.
JSON.stringify()
’s replacer to the rescue!Searching Stack Overflow you may find that there are some large, complicated functions you could write to manage this. But arguably the simplest way to handle it is leveraging
JSON.stringify
—the very thing that’s giving us trouble! More specifically, we can use the little-used 2nd “replacer” parameter inJSON.stringify()
. Let’s start with the following:We’re returning
return value
at the end no matter what. This will cause an infinite loop. But what if, in certain conditions, wereturn undefined
instead?Note: we also wrapped it in
JSON.parse()
because in the end we want an object, not a stringAfter the 10th iteration, we
return undefined
and stop parsing there. While this fixes ourcyclic object
error, this is a bad approach. Objects don’t have numeric indexes, so we can’t predict exactly where to stop!. We need an approach that pays closer attention to the keys being used.Limiting depth by object key
To maintain control over depth and the nesting of
films
anddirector
, let’s keep a track of how many times each appears:In this example, we got to the third
films
and thirddirector
occurrence before it stopped executing! This is the basic premise behind the function.Making it configurable
The only major difference between the above function and the final one is the mapping. In the function, we keep track of how many times each key appears with
count
:If at any point we inspected the value of
count
we’d find something like:Every layer we keep a running tally of the number of times we’ve run across a particular index. See the
""
key? We hit that once at the very beginning, because the object itself didn’t have a key (if that was bothersome we could add logic in the function to take care of that). We also hit a"0"
key whenever we entered thefilms
array.With this object, we can let the consumer of the function specify how many times they’d like to visit each key with the
depth
param.That’s all there is to it! From here, feel free to expand on this function as needed.