4

I have an object with some properties which are String objects:

var obj = {
    foo: new String("bar")
};

I am using String objects because I am needing to store additional sub-properties on the object while still being able to get the string value:

obj.foo.baz = "baz";
"" + obj.foo; //-> "bar";

I feel dumb for asking, but how can I update the value of a String object? Seems like some Array splice magic might need to be applied.

EDIT: Just to be clear, I understand string primitives in JS and immutability therein. This is an object I'm talking about. Here is the test that needs to pass:

assert.equal("" + obj.foo, "foo"); //-> true
assert.equal(obj.foo.baz, "baz"); //-> true

extend(obj, { foo: "foooooo" });
assert.equal("" + obj.foo, "foooooo"); //-> true
assert.equal(obj.foo.baz, "baz"); //-> true
Ryan Wheale
  • 26,022
  • 8
  • 76
  • 96
  • 3
    Possible duplicate of [Are JavaScript strings immutable? Do I need a "string builder" in JavaScript?](http://stackoverflow.com/questions/51185/are-javascript-strings-immutable-do-i-need-a-string-builder-in-javascript) – Ben Feb 16 '16 at 21:34
  • @Ben - string primitives and String objects are two different things. Objects are by far and away mutable, and I need that mutability. – Ryan Wheale Feb 16 '16 at 21:41
  • Maybe you can use a custom class or object instead of a String: `foo: {stringValue: 'bar'};` `obj.foo.stringValue = "baz";` `assert.equal(obj.foo.stringValue, "foo")` . As far as I know String values are not mutable althougt you can add another properties to a String object – sergioFC Feb 16 '16 at 21:56
  • The challenge is you need to retain `String` object because that may have its own prototype methods. I didn't devote too much time to a solution, but please consider what I've posted – vol7ron Feb 16 '16 at 21:57
  • For reference, the ES6 spec defines that the underlying value for a String object is stored in its "[[StringData]] internal slot". The only place this slot is assigned is via the `new String()` constructor, so it is implicitly immutable, though I'm not sure whether it is explicitly described as such. – Jeremy Feb 16 '16 at 23:33

3 Answers3

3

You can't. Strings are immutable, regardless of how you "construct" them (literal or object).

What you should be doing is simply use an actual object to hold your values and your string.

At the most basic level this would be:

var obj = {
  foo: "bar"
};

// later...
obj.baz = "baz";
"" + obj.foo; //-> "bar";

You can also consider using a monadic type as an "amplifier" / decorator, but that seems way overkill for this use case.


As a side note, adding properties and "random" functions to a string object is not a good OOP choice. These are strictly not relevant to the value that is the string, and only make sense to a higher level object, which is where they should reside.

Amit
  • 45,440
  • 9
  • 78
  • 110
  • String objects are indeed mutable. Open your console: `var foo = new String("foo"); foo.bar = "bar"; console.log(foo.bar);` – Ryan Wheale Feb 16 '16 at 21:52
  • Thanks for the architectural addendum. Unfortunately I am dealing with client data in the form of `foo.bar=Foo Bar` and `foo.bar.baz=Foo Bar Baz`. This data is in a newline-delimited text file, and I need to convert it to JS objects for use in code. I can't change the source files. fml – Ryan Wheale Feb 16 '16 at 21:57
  • @RyanWheale - you need to convert it for use in whose code? Yours, or the client's? – Amit Feb 16 '16 at 22:02
  • 1
    @RyanWheale and about mutable or not, string objects are first and foremost objects, and as such are extendable, but you can't mutate the internal value. I guess it's more accurate to say the instance is mutable (like you did), but that's fairly misleading without explaining the details of what can be mutated. – Amit Feb 16 '16 at 22:11
  • thanks. I just made that discovery about String objects and that the data created during construction is indeed immutable. Thanks for the info. – Ryan Wheale Feb 16 '16 at 23:04
2

You'd need to create a new String object and extend any new properties and values to that String object. I've provided a simple example below. That said, this example can be modified to suit your purposes (you'd create a custom extend or setter function).

Example of a property setter function

var createNewString = function (oldStringObj, string) {
  var _new = new String(string);

  var keys = Object.keys(oldStringObj);  // returns only custom properties (not part of prototype)
  for (var i=0,n=keys.length; i<n; i++){
    var key = keys[i];

    if (Number.isInteger(+key)) {
       continue;                         // skip property if it's a numbered key
    }
    _new[key] = oldStringObj[key];       // simple assignment (not a deep copy) -- room for improvement
  }
  return _new;
};

Original object

var obj = {
    foo: new String("bar")
};
obj.foo.baz = "baz"; // new property

Update the object

obj.foo = createNewString( obj.foo, 'foot' );
//obj.foo=='foot' and obj.foo.baz=='baz'
vol7ron
  • 40,809
  • 21
  • 119
  • 172
  • Thanks for all the work you are putting into this. Unfortunately, string objects are more like arrays - each letter is an index and there is a length property. So your function copies old letters. But you've put me in the right direction. It's funny that the constructed String object is immutable: `var foo = new String("foo"); foo[0] = "b"; foo.toString(); //-> "foo";` – Ryan Wheale Feb 16 '16 at 22:09
  • @RyanWheale I may have misunderstood your question; yes the string value is immutable you won't be able to modify/change it. What is it you are asking for? At the end of these operations `obj.foo` will be changed from `String('bar')` to `String('foot')` and retain any new/additional properties you applied to `String('bar')`. I thought that is what you are asking for. – vol7ron Feb 16 '16 at 22:12
  • No you are corrent - you seem to be the only one who understands the question. Your function fails on this: `createNewString(new String("foot"), new String("bar")); //-> "bart"` – Ryan Wheale Feb 16 '16 at 22:15
  • @RyanWheale try this update, but note that this is a simple example and not necessarily a robust solution that will cover every edge case. There are also better implementation techniques, which you may prefer or may have performance benefits over this example. Needless to say, you should adjust as you see fit. – vol7ron Feb 16 '16 at 22:23
1

I suggest that you use a custom type for this, instead of the default String type. The ES6 spec defines that the underlying value for a String object is stored in its "[[StringData]] internal slot". The only place this slot is assigned is via the new String constructor, so it is implicitly immutable. You can create a new type which has the same string-like behaviours that you require, while being mutable.

class MutableString {
  constructor(value) {
    this.value = value;
  }

  toString() {
    return this.value;
  }
}

var obj = {
  foo: new MutableString('bar')
};
obj.foo.baz = "baz";


console.assert("" + obj.foo == "bar");
console.assert(obj.foo + "" == "bar");
console.assert(obj.foo.baz == "baz");
console.assert(Object.keys({[obj.foo]: 1})[0] == "bar");

obj.foo.value = "foooooo";
console.assert("" + obj.foo == "foooooo");
console.assert(obj.foo + "" == "foooooo");
console.assert(obj.foo.baz == "baz");
console.assert(Object.keys({[obj.foo]: 1})[0] == "foooooo");

Because this isn't really a string it won't support any string methods, so it may not be suitable for your use. But it's more flexible and a little clearer, so I suggest considering it if possible.

It may be necessary to define a valueOf() method like toString() to handle some other cases, but I haven't verified.

Jeremy
  • 1
  • 85
  • 340
  • 366
  • Thanks, this is very useful. I was hoping for something that didn't require making my own class. It's trivial to copy the String.prototype onto this - you would just have to String.prototype.fn.apply each one to this.value. I will keep this in my back pocket for sure. – Ryan Wheale Feb 17 '16 at 00:20