Repository: fastify/fast-json-stringify (JavaScript)

Issue:
Support for toJSON
I'd like to have a little different model structure internally in my app than returning it externally via API. Unfortunately, using `fastify` and `fast-json-stringify` this can't be done as `fast-json-stringify` does not use the objects `toJSON` function to map the data somehow.

I guess it could be on purpose to improve the performance, but this is a little bit unintuitive to do: 

`reply.send(model.toJSON());`

and what even worse:

```js
const models = [arrayOfModels];
reply.send(models.map(model => model.toJSON()));
```

This doesn't make much sense as there's no difference if I have to do this manually or if it would be done by `fast-json-stringify` - just as I'd expect.

Here's a little reproduction:

```js
const fastJson = require("fast-json-stringify");

const stringify = fastJson({
    type: "object",
    properties: {
        groups: {
            type: "array",
            items: {
                type: "string"
            }
        }
    }
});

class User {
    constructor() {
        this.groups = [{ name: "admin" }, { name: "subscriber" }];
    }

    toJSON() {
        return {
            ...this,
            groups: this.groups.map(group => group.name)
        };
    }
}

const user = new User();

console.log(stringify(user)); // returns: {"groups":["[object Object]","[object Object]"]}
console.log(JSON.stringify(user)); // returns: {"groups":["admin","subscriber"]}
```

Could you somehow relate to this issue? And maybe you have some idea for a solution in `fastify`?


Discussion:
If it is not too costly perf-wise, we should check if an object has a toJSON method, and call that before applying the schema. Would you like to send a PR that does that?
@cemremengu Oeps. I guess we both did the same work 😄 
@sarneeh Yes we even pushed the same second. Sorry it's my bad I was just lurking around and saw this, I should have asked first.  It is your right to have this PR 😄 
@cemremengu No problem 😄 I needed some time to find out what's going on in this project. I hope I didn't mess this up completely 😄 
@sarneeh I will close mine in favor of yours and we can continue work from there
@mcollina Awesome, thank you for the fast publish 🎉 
Glad to help! Keep sending PRs :).



Patch:
diff --git a/index.js b/index.js
index 61be0640..44dd5344 100644
--- a/index.js
+++ b/index.js
@@ -554,9 +554,17 @@ function addIfThenElse (schema, name, externalSchema, fullSchema) {
   return { code: code, laterCode: laterCode }
 }
 
+function toJSON (variableName) {
+  return `typeof ${variableName}.toJSON === 'function' 
+    ? ${variableName}.toJSON() 
+    : ${variableName}
+  `
+}
+
 function buildObject (schema, code, name, externalSchema, fullSchema) {
   code += `
-    function ${name} (obj) {
+    function ${name} (input) {
+      var obj = ${toJSON('input')}
       var json = '{'
       var addComma = false
   `


Test patch:
diff --git a/test/toJSON.test.js b/test/toJSON.test.js
new file mode 100644
index 00000000..25fd23d4
--- /dev/null
+++ b/test/toJSON.test.js
@@ -0,0 +1,83 @@
+'use strict'
+
+const test = require('tap').test
+const build = require('..')
+
+test('use toJSON method on object types', (t) => {
+  t.plan(1)
+
+  const stringify = build({
+    title: 'simple object',
+    type: 'object',
+    properties: {
+      productName: {
+        type: 'string'
+      }
+    }
+  })
+  const object = {
+    product: { name: 'cola' },
+    toJSON: function () {
+      return { productName: this.product.name }
+    }
+  }
+
+  t.equal('{"productName":"cola"}', stringify(object))
+})
+
+test('use toJSON method on nested object types', (t) => {
+  t.plan(1)
+
+  const stringify = build({
+    title: 'simple array',
+    type: 'array',
+    items: {
+      type: 'object',
+      properties: {
+        productName: {
+          type: 'string'
+        }
+      }
+    }
+  })
+  const array = [
+    {
+      product: { name: 'cola' },
+      toJSON: function () {
+        return { productName: this.product.name }
+      }
+    },
+    {
+      product: { name: 'sprite' },
+      toJSON: function () {
+        return { productName: this.product.name }
+      }
+    }
+  ]
+
+  t.equal('[{"productName":"cola"},{"productName":"sprite"}]', stringify(array))
+})
+
+test('not use toJSON if does not exist', (t) => {
+  t.plan(1)
+
+  const stringify = build({
+    title: 'simple object',
+    type: 'object',
+    properties: {
+      product: {
+        type: 'object',
+        properties: {
+          name: {
+            type: 'string'
+          }
+        }
+      }
+    }
+  })
+  const object = {
+    product: { name: 'cola' }
+  }
+
+  t.equal('{"product":{"name":"cola"}}', stringify(object))
+})