I'm using decimal.js for some financial calculations in Node.  I'm writing a custom JSON.stringify replacer function, but when I test the property types using instanceof, I get a different result than when I do the same test outside of the replacer function.
Here's a runnable example:
const myObj = {
    myNum: new Decimal(0.3)
};
// logs 'Property "myNum" is a Decimal: true'
console.log('Property "myNum" is a Decimal:', myObj.myNum instanceof Decimal);
const replacer = (key, value) => {
    if (key === 'myNum') {
        // logs 'Property "myNum" is a Decimal: false'
        console.log('Property "myNum" is a Decimal:', value instanceof Decimal);
    }
    if (value instanceof Decimal) {
        return value.toNumber()
    } else {
        return value;
    }
}
JSON.stringify(myObj, replacer, 4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.0.0/decimal.js"></script>
Why is this happening?
If I replace the Decimal instance with an instance of my own custom class, both instanceof tests behave the same, as expected:
function MyClass() {}
const myObj = {
    myClass: new MyClass()
};
// logs 'Property "myClass" is a MyClass: true'
console.log('Property "myClass" is a MyClass:', myObj.myClass instanceof MyClass);
const replacer = (key, value) => {
    if (key === 'myClass') {
        // logs 'Property "myClass" is a MyClass: true'
        console.log('Property "myClass" is a MyClass:', value instanceof MyClass);
    }
    return value;
}
JSON.stringify(myObj, replacer, 4);
Figured it out.  The Decimal instance includes a .toJSON() method. When JSON.stringify encounters an object that defines a toJSON function, it calls it and returns the result as the second parameter in the replacer function instead of the object reference.  As a result, the value variable in my example above pointed to a string, not a Decimal instance.
From MDN:
If an object being stringified has a property named
toJSONwhose value is a function, then thetoJSON()method customizes JSON stringification behavior: instead of the object being serialized, the value returned by thetoJSON()method when called will be serialized.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior
To demonstrate this, I can tweak my second example above to include a toJSON function:
function MyClass() {
    // add a toJSON method to my custom class
    this.toJSON = () => {
        return 'Hello, world!';
    };
};
const myObj = {
    myClass: new MyClass()
};
// logs 'Property "myClass" is a MyClass: true'
console.log('Property "myClass" is a MyClass:', myObj.myClass instanceof MyClass);
const replacer = (key, value) => {
    if (key === 'myClass') {
        // logs 'Property "myClass" is a MyClass: true'
        console.log('Property "myClass" is a MyClass:', value instanceof MyClass);
    }
    return value;
}
JSON.stringify(myObj, replacer, 4);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With