4

I read many posts on stackoverflow and other tutorial sites for writing object/JSON to a file, but none of the solutions worked for me.

1) Code:

let messages = await fetchMessages()
console.log(messages)    // Prints an object
fs.writeFileSync('./msgdata.json', messages , 'utf-8'); 

2) Also tried,

fs.writeFileSync('./msgdata.json', JSON.stringify(messages) , 'utf-8'); 

3) Also tried,

fs.writeFile(), but get same output as above.

msgdata.json: (For 1)

[object Map]

msgdata.json: (For 2 & 3)

{}

Can someone please point out what could be causing this?

## Output of console.log(messages) ##

OUTPUT contains 10 more such objects having different id's:

channel:
   TextChannel {
     type: 'text',
     id: '424825532274253312',
     name: 'request',
     position: 69,
     parentID: '397363224286473987',
     permissionOverwrites: [Object],
     topic: null,
     nsfw: false,
     lastMessageID: '427143105410760704',
     guild: [Object],
     messages: [Object],
     _typing: Map {} },
  id: '427142596817846272',
  type: 'DEFAULT',
  content: '**Select your emoji:** __***Group 9:***__',
  author:
   ClientUser {
     id: '407083773537350272',
     username: 'Bot',
     discriminator: '9256',
     avatar: '9c374e719ba2ab4e69fd577005b635bf',
     bot: true,
     lastMessageID: null,
     lastMessage: null,
     verified: true,
     email: null,
     localPresence: {},
     _typing: Map {},
     friends: Collection {},
     blocked: Collection {},
     notes: Collection {},
     premium: null,
     mfaEnabled: false,
     mobile: null,
     settings: [Object],
     guildSettings: Collection {} },
  member:
   GuildMember {
     guild: [Object],
     user: [Object],
     _roles: [Array],
     serverDeaf: false,
     serverMute: false,
     selfMute: undefined,
     selfDeaf: undefined,
     voiceSessionID: undefined,
     voiceChannelID: undefined,
     speaking: false,
     nickname: null,
     joinedTimestamp: 1517127343434,
     lastMessageID: null,
     lastMessage: null },
  pinned: false,
  tts: false,
  nonce: undefined,
  system: false,
  embeds: [],
  attachments: Collection {},
  createdTimestamp: 1521909131007,
  editedTimestamp: null,
  reactions:
   Collection {
     'taillow:417281639777959940' => [Object],
     'shroomish:417281639899463680' => [Object],
     'sableye:417281640197521419' => [Object],
     'ralts:417281642735075329' => [Object],
     'sentret:417281644001624076' => [Object],
     'shuppet:417281644291162132' => [Object],
     'torchic:417281647210397706' => [Object],
     'snubbull:417281647692480522' => [Object],
     'sunkern:417281647763783681' => [Object],
     'slowpoke:417281648653107200' => [Object],
     'teddiursa:417281649537974273' => [Object],
     'sneasel:417281649613471747' => [Object],
     'snorunt:417281649819123712' => [Object],
     'surskit:417281650163056640' => [Object],
     'qwilfish:417281654629859348' => [Object],
     'shelgon:417281654730522624' => [Object] },
  mentions:
   MessageMentions {
     everyone: false,
     users: Collection {},
     roles: Collection {},
     _content: '**Select your emoji:** __***Group
9:***__',
     _client: [Object],
     _guild: [Object],
     _members: null,
     _channels: null },
  webhookID: null,
  hit: null,
  _edits: [] },

3 Answers3

5

This is because the object returned is a Map object (MDN). When you JSON.strigify a Map object, it always returns {}. There are two ways to get readable JSON that stores your map. The first is to loop over all the entries and create a JSON (taking in consideration the keys and values). That should be simple to achieve.

Another way is to create an array from the Map and then stringify it.

fs.writeFileSync('./msgdata.json', JSON.stringify([...myObject]) , 'utf-8'); 
Shiven Sinha
  • 656
  • 5
  • 14
  • Thanks for the answer, I'll try it out and update here –  Mar 25 '18 at 11:01
  • `(node:4252) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Converting circular structure to JSON (node:4252) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.` Tried your solution and i am getting this error... –  Mar 25 '18 at 12:04
  • @eNthUsiAst Could you please mention what `console.log(messages)` prints? – Shiven Sinha Mar 25 '18 at 14:39
  • Added it @Shiven –  Mar 25 '18 at 16:27
  • @eNthUsiAst Well, wow. The `[object Map]` seems to be weird, because the output seems to be JSON. But actually, could you tell me what `console.log(JSON.stringify(messages))` instead of the straightforward log prints? I believe it would be empty, is it? – Shiven Sinha Mar 26 '18 at 10:43
  • @eNthUsiAst Oh, I can't believe I missed this. Could you change the code to `fetchMessages().then((messages) => console.log(JSON.stringify(messages)))` – Shiven Sinha Mar 27 '18 at 15:28
0

You can define Map.prototype.toJSON which works, but it isn't standard and is discouraged. It is automatically called by JSON.stringify if it finds it.

Map.prototype.toJSON = function() {return [...this];};
console.log(JSON.stringify(messages));

A better way is to define your own replacer function (and reviver function too if you need to reconstruct map from JSON). Here I also wrap the map representing array in a special object to tell maps and ordinary arrays apart in JSON:

function replacer (key, value) {
    if (value instanceof Map) {
        return {
            _type: "map",
            map: [...value],
        }
    } else return value;
}

function reviver (key, value) {
    if (value._type == "map") return new Map(value.map);
    else return value;
}

let str = JSON.stringify(messages, replacer);
let msg = JSON.parse(str, reviver);

The error you are getting about circular structure means that some objects/arrays there contain references to itself. When you store messages you don't need to store all the information related to the channels etc and instead only store ids. So that means extending replacer for TextChannels, ClientUsers, GuildMembers etc:

if (value instanceof TextChannel) {
  return {
    _type: "TextChannel",
    id: value.id,
  };
}
Andrew Svietlichnyy
  • 743
  • 1
  • 6
  • 13
  • Wow! This was really cool thing I learnt. What is the `...value` syntax? And if i use the first approach Map.prototype.toJSON, then how do i call it? –  Mar 25 '18 at 11:01
  • JSON.stringify will call toJSON method when it's present on a map. [...value] is a spread syntax (ES6+) and will convert map to array [[key, value], [key value],...] – Andrew Svietlichnyy Mar 25 '18 at 11:21
  • Okay thanks understood...Getting this error while trying your solution... ``(node:4252) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Converting circular structure to JSON (node:4252) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.` Tried your solution and i am getting this error...` –  Mar 25 '18 at 12:06
  • But what is each individual message type (stuff before `channel:`)? You need a replacer either for "foreign" classes (as I put in my edit) or for message itself to replace "foreign" classes by their ids. – Andrew Svietlichnyy Mar 26 '18 at 12:55
  • I am not sure if i understood it, will it be possible for you to edit your answer how should i do it in actual? :( –  Mar 27 '18 at 15:00
  • It depends on what data you need and how you want to use it afterwards. It's also beyond the scope of original question (=worth a new one) – Andrew Svietlichnyy Mar 30 '18 at 08:50
0
var util = require('util');
fs.writeFileSync('./data.json', util.inspect(obj) , 'utf-8');

Ref. Write objects into file with Node.js

Erçin Dedeoğlu
  • 4,950
  • 4
  • 49
  • 69
adoan
  • 1