function encodeObject(msg,opts) {
    try {
        var debuglength = 1000;
        if (opts && hasOwnProperty.call(opts, 'maxLength')) {
            debuglength = opts.maxLength;
        }
        var msgType = typeof msg.msg;
        if (msg.msg instanceof Error || /Error/.test(msg.msg?.__proto__?.name)) {
            msg.format = "error";

            const cause = msg.msg.cause
            const value = {
                __enc__: true,
                type: 'error',
                data: {
                    name: msg.msg.name,
                    message: hasOwnProperty.call(msg.msg, 'message') ? msg.msg.message : msg.msg.toString(),
                    code: msg.msg.code,
                    cause: cause + "",
                    stack: msg.msg.stack,
                }
            }
            // We optimistically added these properties to the object
            // to ensure they are shown above 'stack'. But we don't want
            // to include them if they are undefined 
            if (cause === undefined) {
                delete value.data.cause
            }
            if (msg.msg.code === undefined) {
                delete value.data.code
            }
            msg.msg = JSON.stringify(value);
        } else if (msg.msg instanceof Buffer) {
            msg.format = "buffer["+msg.msg.length+"]";
            msg.msg = msg.msg.toString('hex');
            if (msg.msg.length > debuglength) {
                msg.msg = msg.msg.substring(0,debuglength);
            }
        } else if (msg.msg && msgType === 'object') {
            try {
                msg.format = constructorName(msg.msg) || "Object";
                // Handle special case of msg.req/res objects from HTTP In node
                if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
                    msg.format = "Object";
                }
            } catch(err) {
                msg.format = "Object";
            }
            if (/error/i.test(msg.format)) {
                // TODO: check if this is needed
                msg.msg = JSON.stringify({
                    name: msg.msg.name,
                    message: msg.msg.message
                });
            } else {
                var isArray = Array.isArray(msg.msg);
                var needsStringify = isArray;
                if (isArray) {
                    msg.format = "array["+msg.msg.length+"]";
                    if (msg.msg.length > debuglength) {
                        // msg.msg = msg.msg.slice(0,debuglength);
                        msg.msg = {
                            __enc__: true,
                            type: "array",
                            data: msg.msg.slice(0,debuglength),
                            length: msg.msg.length
                        }
                    }
                } else if (constructorName(msg.msg) === "Set") {
                    msg.format = "set["+msg.msg.size+"]";
                    msg.msg = {
                        __enc__: true,
                        type: "set",
                        data: Array.from(msg.msg).slice(0,debuglength),
                        length: msg.msg.size
                    }
                    needsStringify = true;
                } else if (constructorName(msg.msg) === "Map") {
                    msg.format = "map";
                    msg.msg = {
                        __enc__: true,
                        type: "map",
                        data: Object.fromEntries(Array.from(msg.msg.entries()).slice(0,debuglength)),
                        length: msg.msg.size
                    }
                    needsStringify = true;
                } else if (constructorName(msg.msg) === "RegExp") {
                    msg.format = 'regexp';
                    msg.msg = msg.msg.toString();
                }
                if (needsStringify || (msg.format === "Object")) {
                    msg.msg = safeJSONStringify(msg.msg, function(key, value) {
                        if (key === '_req' || key === '_res') {
                            value = {
                                __enc__: true,
                                type: "internal"
                            }
                        } else if (value instanceof Error || /Error/.test(value?.__proto__?.name)) {
                            const cause = value.cause
                            const code = value.code
                            value = {
                                __enc__: true,
                                type: 'error',
                                data: {
                                    name: value.name,
                                    message: hasOwnProperty.call(value, 'message') ? value.message : value.toString(),
                                    code,
                                    cause: cause + "",
                                    stack: value.stack,
                                }
                            }
                            // We optimistically added these properties to the object
                            // to ensure they are shown above 'stack'. But we don't want
                            // to include them if they are undefined 
                            if (cause === undefined) {
                                delete value.data.cause
                            }
                            if (code === undefined) {
                                delete value.data.code
                            }
                        } else if (Array.isArray(value) && value.length > debuglength) {
                            value = {
                                __enc__: true,
                                type: "array",
                                data: value.slice(0,debuglength),
                                length: value.length
                            }
                        } else if (typeof value === 'string') {
                            if (value.length > debuglength) {
                                value = value.substring(0,debuglength)+"...";
                            }
                        } else if (typeof value === 'function') {
                            value = {
                                __enc__: true,
                                type: "function"
                            }
                        } else if (typeof value === 'number') {
                            if (isNaN(value) || value === Infinity || value === -Infinity) {
                                value = {
                                    __enc__: true,
                                    type: "number",
                                    data: value.toString()
                                }
                            }
                        } else if (typeof value === 'bigint') {
                            value = {
                                __enc__: true,
                                type: 'bigint',
                                data: value.toString()
                            }
                        } else if (value && value.constructor) {
                            if (value.type === "Buffer") {
                                value.__enc__ = true;
                                value.length = value.data.length;
                                if (value.length > debuglength) {
                                    value.data = value.data.slice(0,debuglength);
                                }
                            } else if (constructorName(value) === "ServerResponse") {
                                value = "[internal]"
                            } else if (constructorName(value) === "Socket") {
                                value = "[internal]"
                            } else if (constructorName(value) === "Set") {
                                value = {
                                    __enc__: true,
                                    type: "set",
                                    data: Array.from(value).slice(0,debuglength),
                                    length: value.size
                                }
                            } else if (constructorName(value) === "Map") {
                                value = {
                                    __enc__: true,
                                    type: "map",
                                    data: Object.fromEntries(Array.from(value.entries()).slice(0,debuglength)),
                                    length: value.size
                                }
                            } else if (constructorName(value) === "RegExp") {
                                value = {
                                    __enc__: true,
                                    type: "regexp",
                                    data: value.toString()
                                }
                            }
                        } else if (value === undefined) {
                            value = {
                                __enc__: true,
                                type: "undefined",
                            }
                        }
                        return value;
                    });
                } else {
                    try {
                        msg.msg = msg.msg.toString();
                    } catch(e) {
                        msg.msg.format = 'error'
                        msg.msg = JSON.stringify({
                            __enc__: true,
                            type: 'error',
                            data: {
                                message: "[Type not serializable]",
                                stack: e.stack
                            }
                        })
                    }
                }
            }
        } else if (msgType === "function") {
            msg.format = "function";
            msg.msg = "[function]"
        } else if (msgType === "boolean") {
            msg.format = "boolean";
            msg.msg = msg.msg.toString();
        } else if (msgType === "number") {
            msg.format = "number";
            msg.msg = msg.msg.toString();
        } else if (msgType === "bigint") {
            msg.format = "bigint";
            msg.msg = {
                __enc__: true,
                type: 'bigint',
                data: msg.msg.toString()
            };
        } else if (msg.msg === null || msgType === "undefined") {
            msg.format = (msg.msg === null)?"null":"undefined";
            msg.msg = "(undefined)";
        } else {
            msg.format = "string["+msg.msg.length+"]";
            if (msg.msg.length > debuglength) {
                msg.msg = msg.msg.substring(0,debuglength)+"...";
            }
        }
        return msg;
    } catch(e) {
        msg.format = "error";
        const errorMsg = {
            __enc__: true,
            type: 'error',
            data: {
                name: e.name,
                message: 'encodeObject Error: ' + (hasOwnProperty.call(e, 'message') ? e.message : e.toString()),
                stack: e.stack,
            }
        }
        msg.msg = JSON.stringify(errorMsg);
        return msg;
    }
}
Encode an object to JSON without losing information about non-JSON types
such as Buffer and Function.

*This function is closely tied to its reverse within the editor*

@param  {Object} msg
@param {Object} opts
@return {Object} the encoded object
@memberof @node-red/util_util