simone
simone

Reputation: 5227

Is it possible to automatically stringify in JavaScript?

I know I can create a toString() function on an object, so that every time it's printed or treated like a string it will first stringify the object with that function.

Is it possible to do that directly so I can use String object functions on the object?

var SomeObject = function(a, b){
    this.a = a;
    this.b = b
}

SomeObject.prototype.toString = function(){
    return [ this.a, this.b ].join(' ')
}

var objInstance = new SomeObject('this', 'that');

console.log(objInstance + '')                // This that
console.log(("" + objInstance).split(''))    // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split())             // Error

Is it possible to do so that the object "behaves" like a string when a String function is called on it?

In other words, I'd like objInstance.split() to have the same result as ("" + objInstance).split(''), and also objInstance.length or objInstance.match(/something/), etc.

Upvotes: 18

Views: 2692

Answers (3)

Bergi
Bergi

Reputation: 665121

You can let your objects inherit from String so that all string methods become available:

class SomeObject extends String {
  constructor(a, b) {
    super(a + " " + b);
    this.a = a;
    this.b = b;
  }
}

var obj = new SomeObject('this', 'that');
console.log(obj.split(""));

No need to use complicated Proxy solutions :-)

All the String.prototype methods (except for .toString, .valueOf and [Symbol.iterator]) are "intentionally generic; [they do] not require that its this value be a String object. Therefore, [they] can be transferred to other kinds of objects for use as a method." You can call them on any value, they will coerce it to a string (using .toString() or .valueOf as usual).

You don't even need to use ES6 class extends to inherit from the builtin (which also makes your string value immutable), it works in ES5 as well:

function SomeObject(a, b) {
  this.a = a;
  this.b = b;
}
SomeObject.prototype = Object.create(String.prototype);
SomeObject.prototype.constructor = SomeObject;
SomeObject.prototype.toString = function() {
    return this.a + " " + this.b;
};

var obj = new SomeObject('this', 'that');
console.log(obj.split(""));

Upvotes: 29

Just code
Just code

Reputation: 13801

One option to extend the SomeObject too, something like this.

var SomeObject = function(a, b){
    this.a = a;
    this.b = b
}

SomeObject.prototype.toString = function(){
    return [ this.a, this.b ].join(' ')
};

SomeObject.prototype.split = function() {
   return String.prototype.split.apply(this.toString(), arguments);
};

var objInstance = new SomeObject('this', 'that');

console.log(objInstance + '')                // this that
//console.log(("" + objInstance).split(''))    // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split(''));

In a comment you've asked:

I was thinking about doing this programmatically by doing it for all functions - but is there a way to list all functions of an object?

Yes, you'd use getOwnPropertyNames on String.prototype and filter out the ones that aren't functions:

var SomeObject = function(a, b){
    this.a = a;
    this.b = b
}

SomeObject.prototype.toString = function(){
    return [ this.a, this.b ].join(' ')
};

Object.getOwnPropertyNames(String.prototype).forEach(function(name) {
    var fn = String.prototype[name];
    if (name !== "toString" && typeof fn === "function") {
        SomeObject.prototype[name] = function() {
            return fn.apply(this.toString(), arguments);
        };
    }
});

var objInstance = new SomeObject('this', 'that');

console.log(objInstance + '')                // this that
//console.log(("" + objInstance).split(''))    // [ 't', 'h', 'i', 's', ' ', 't', 'h', 'a', 't' ]
console.log(objInstance.split(''));

Upvotes: 2

CertainPerformance
CertainPerformance

Reputation: 371039

One option would be to return a Proxy that checks whether the property exists on String.prototype, and if it does, calls that property with the string that represents the object:

// Declare the proxy handler up front here
// to avoid unnecessary creation of duplicate handler objects
const handler = {
  get(obj, prop) {
    if (obj[prop] !== undefined) {
      return obj[prop];
    }
    const stringMethod = String.prototype[prop];
    if (stringMethod) {
      return stringMethod.bind(obj.a + ' ' + obj.b);
    }
  },
};

const SomeClass = function(a, b) {
  this.a = a;
  this.b = b
  return new Proxy(this, handler);
}

const instance = new SomeClass('this', 'that');

// String methods:
console.log(instance.trim());
console.log(instance.includes('this'));
console.log(instance.includes('somethingelse'));
console.log(instance.split(''));

// Can still assign and retrieve values directly on the object as normal:
instance.foo = 'foo';
console.log(instance.foo);

Upvotes: 22

Related Questions