0

In order to prevent that error regarding circular structure to JSON I have this function:

circular = () => { //fix circular stuff for json.stringify
  seen = new WeakSet();
  return (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

it works fairly well except in the case below:

var gameon = 1;
var fighter1 = {"userid":"97","username":"john","items":{},"ailments":{}};
var fighter2 = {"userid":"91","username":"james","items":{},"ailments":{}};
var resume = 30;

all = {"gameon":gameon,"fighter1":fighter1,"fighter2":fighter2,"resume":resume,"inturn":fighter1,"outturn":fighter2};
    
console.log(JSON.stringify(all,circular()));

it will print something like this:

{"gameon":1,"fighter1":{"userid":"97","username":"john","items":{},"ailments":{}},"fighter2":{"userid":"91","username":"james","items":{},"ailments":{}},"resume":30}"

Please notice how the output truncates after resume.

If I don't use the circular function then I get the correct output but also the "circular structure" error if I try to write to a file.

Why is that?

halfer
  • 19,824
  • 17
  • 99
  • 186
Cain Nuke
  • 2,843
  • 5
  • 42
  • 65
  • 2
    maybe irrelevant to the question, but where's the circularity in your example? I couldn't see any, so just pasted it into the browser console and tried `JSON.stringify(all)`, and got the expected JSON string with no errors. – Robin Zigmond Feb 08 '23 at 22:06
  • @RobinZigmond There are mutliple references to the same `fighter1` and `fighter2` objects. – Barmar Feb 08 '23 at 22:09
  • it only happens as you try to write that to a file. like this: `fs.writeFileSync(file,JSON.stringify(all,circular()),{encoding:'utf8',flag:'w'}); ` – Cain Nuke Feb 08 '23 at 22:09
  • and when using your `circular` function, it is surely expected that the output is "truncated" as you put it, since the remaining values (`fighter1` and `fighter2`) have already occurred in the output and your function is explicitly designed to therefore exclude them. I think you're radically misunderstanding something about JSON and how the replacer function works in `JSON.stringify` (the latter is fair enough, I've never seen it in practice and just had to remind myself how it works) - but I'm not sure what. – Robin Zigmond Feb 08 '23 at 22:09
  • 1
    @Barmar that's true but it's not circular - as I observed, it stringifies absolutely fine, just repeating the ouput. "circular" to me in this context means something like `const obj = { key: obj }; JSON.stringify(obj);` which will give this error. – Robin Zigmond Feb 08 '23 at 22:10
  • but why does it happen only as I try to write to file? – Cain Nuke Feb 08 '23 at 22:10
  • 1
    Your function returns `undefined` if the value has already been seen. As the [documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) says: **If you return a Function, Symbol, or undefined, the property is not included in the output.** – Barmar Feb 08 '23 at 22:11
  • I'm afraid I don't know - you don't show us the code where you try to write this to a file. – Robin Zigmond Feb 08 '23 at 22:11
  • @RobinZigmond Circularity is often generalized to any shared references, since parsing the JSON will lose that sharing. – Barmar Feb 08 '23 at 22:12
  • 2
    Your code is doing exactly what you told it to do. It doesn't put the same object in the JSON multiple times. I suspect you only want to remove nested circularities (because they cause infinite recursion), not multiple references to the same object. But the replacer can't tell the difference. – Barmar Feb 08 '23 at 22:27
  • no, it has the exact same function I posed here. – Cain Nuke Feb 08 '23 at 22:34
  • why open a new question then? why not comment there? – balexandre Feb 08 '23 at 22:35
  • the problem is that you are returning nothing, and that's why, all properties with a circular reference, will show nothing, as JSON.stringingy will remove all empty properties... you can, instead of `return:` write `return '[Circular]';` for example, but if you still need the full value, you need to store it and return that copied value – balexandre Feb 08 '23 at 22:44
  • can't you just use the NodeJs function? `fs.writeFileSync(file, inspect(all), { encoding: 'utf8', flag: 'w' });`? you can get `inspect` from `const { inspect } = require('node:util');` it will show the references and point out what is circular – balexandre Feb 08 '23 at 22:57
  • @balexandre It's already commented under the accepted answer and under another answer. This approach is buggy and doesn't work. It's shown with very simple examples. – jabaa Feb 08 '23 at 23:12
  • What's the actual question? Do you want to know why your approach doesn't work? You can find the explanation in comments under the answers: https://stackoverflow.com/a/11616993/16540390 and https://stackoverflow.com/a/53731154/16540390. Do you want a working solution? You can use a different answer: https://stackoverflow.com/a/18354289/16540390 – jabaa Feb 08 '23 at 23:13
  • inspect seems to work for now. But why all the extra spaces in the output? – Cain Nuke Feb 09 '23 at 00:09

1 Answers1

0

Your circular function returns undefined for any already seen objects. JSON.stringify() comes across undefined and it stops processing the rest of the object. You can try to modify your function to add a placeholder for circular references:

   const circular = () => {
      let seen = new WeakSet();
      let placeholder = {};
      return (key, value) => {
        if (typeof value === 'object' && value !== null) {
          if (seen.has(value)) {
            return placeholder;
          }
          seen.add(value);
        }
        return value;
      };
    };
protob
  • 3,317
  • 1
  • 8
  • 19
  • Thanks but even though I get no errors it turns out `inturn` and `output` return a {} value nstead of the actual values. – Cain Nuke Feb 08 '23 at 23:56