2

I have an object that contains a getter.

myObject {
    id: "MyId",
    get title () { return myRepository.title; }
}

myRepository.title = "MyTitle";

I want to obtain an object like:

myResult = {
    id: "MyId", 
    title: "MyTitle"
}

I don't want to copy the getter, so:

myResult.title;                       // Returns "MyTitle"
myRepository.title = "Another title";
myResult.title;                       // Should still return "MyTitle"

I've try:

  • $.extend(): But it doesn't iterate over getters. http://bugs.jquery.com/ticket/6145
  • Iterating properties as suggested here, but it doesn't iterate over getters.
  • As I'm using angular, using Angular.forEach, as suggested here. But I only get properties and not getters.

Any idea? Thx!

Update I was setting the getter using Object.defineProperty as:

 "title": { get: function () { return myRepository.title; }},

As can be read in the doc:

enumerable true if and only if this property shows up during enumeration of the properties on the corresponding object. Defaults to false.

Setting enumerable: true fix the problem.

"title": { get: function () { return myRepository.title; }, enumerable: true },
Community
  • 1
  • 1
Mario Levrero
  • 3,303
  • 4
  • 23
  • 57
  • 3
    Sorry, but your object is a syntax error. You need to remove `:function` after a `get` keyword; and you cannot have a normal `title` property and a getter with the same name. – Bergi Nov 11 '14 at 11:19
  • 2
    `$.extend` does work very well. The bug description says "*jQuery.extend copies property getter return values, not their code.*" - however, that's just what you want. Doing it manually with `for in` will do the same. – Bergi Nov 11 '14 at 11:21
  • Sorry @Bergi, the object sintax is just a copy of the console.log for the object. I'm going to update the question. – Mario Levrero Nov 11 '14 at 11:23

3 Answers3

4

$.extend does exactly what you want. (Update: You've since said you want non-enumerable properties as well, so it doesn't do what you want; see the second part of this answer below, but I'll leave the first bit for others.) The bug isn't saying that the resulting object won't have a title property, it's saying that the resulting object's title property won't be a getter, which is perfect for what you said you wanted.

Example with correct getter syntax:

// The myRepository object
var myRepository = { title: "MyTitle" };

// The object with a getter
var myObject = {
    id: "MyId",
    get title() { return myRepository.title; }
};

// The copy with a plain property
var copy = $.extend({}, myObject);

// View the copy (although actually, the result would look
// the same either way)
snippet.log(JSON.stringify(copy));

// Prove that the copy's `title` really is just a plain property:
snippet.log("Before: copy.title = " + copy.title);
copy.title = "foo";
snippet.log("After:  copy.title = " + copy.title);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Syntax fixes:

  • Added missing var, =, and ;

  • Removed duplicate property title

  • Corrected the getter declaration syntax


If you want to include non-enumerable properties, you'll need to use Object.getOwnPropertyNames because they won't show up in a for-in loop, Object.keys, or $.extend (whether or not they're "getter" or normal properties):

// The myRepository object
var myRepository = { title: "MyTitle" };

// The object with a getter
var myObject = {
    id: "MyId"
};
Object.defineProperty(myObject, "title", {
  enumerable: false, // it's the default, this is just for emphasis,
  get: function() {
    return myRepository.title;
  }
});

snippet.log("$.extend won't visit non-enumerable properties, so we only get id here:");
snippet.log(JSON.stringify($.extend({}, myObject)));

// Copy it
var copy = Object.getOwnPropertyNames(myObject).reduce(function(result, name) {
  result[name] = myObject[name];
  return result;
}, {});

// View the copy (although actually, the result would look
// the same either way)
snippet.log("Our copy operation with Object.getOwnPropertyNames does, though:");
snippet.log(JSON.stringify(copy));

// Prove that the copy's `title` really is just a plain property:
snippet.log("Before: copy.title = " + copy.title);
copy.title = "foo";
snippet.log("After:  copy.title = " + copy.title);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
T.J. Crowder
  • 959,406
  • 173
  • 1,780
  • 1,769
  • Thx! I've update the question. The problem was that $.extend doesn't work with getters if property.enumerable = false; – Mario Levrero Nov 11 '14 at 12:38
  • @MarioLevrero: True, they also won't show up in a `for-in` loop like Scimonter's. If you want to know about non-enumerable properties, you'll want to use [`Object.getownPropertyNames`](http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.3.4). – T.J. Crowder Nov 11 '14 at 13:11
  • 1
    @MarioLevrero: I've updated the answer to show how to use `getOwnPropertyNames` (well, one of the ways). – T.J. Crowder Nov 11 '14 at 13:17
  • Yes, thanks for your updated. I didn't notice the false by default enumerable property. – Mario Levrero Nov 11 '14 at 13:55
  • @MarioLevrero: I was surprised by that too, when I first learned about `defineProperty`. – T.J. Crowder Nov 11 '14 at 14:01
3

First of all, fix your syntax, though it probably is good in your actual code:

myObject = {
    id: "MyId",
    get title () { return myRepository.title; }
}

Now, to the answer. :)

You can just use a for..in loop to get all the properties, then save them as-is:

var newObj = {};
for (var i in myObject) {
    newObj[i] = myObject[i];
}

No jQuery, Angular, any other plugins needed!

Scimonster
  • 31,931
  • 8
  • 74
  • 86
0

I had the same issue but in TypeScript and the method mentioned by T.J. Crowder didnt work.

What did work was the following:

TypeScript:

function copyObjectIncludingGettersResult(originalObj: any) {

    //get the properties
    let copyObj = Object.getOwnPropertyNames(originalObj).reduce(function (result: any, name: any) {
        result[name] = (originalObj as any)[name];
        return result;
    }, {});

    //get the getters values
    let prototype = Object.getPrototypeOf(originalObj);
    copyObj = Object.getOwnPropertyNames(prototype).reduce(function (result: any, name: any) {
        //ignore functions which are not getters
        let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
        if (descriptor?.writable == null) {
            result[name] = (originalObj as any)[name];
        }
        return result;
    }, copyObj);

    return copyObj;
}

Javascript version:

function copyObjectIncludingGettersResult(originalObj) {
  
    //get the properties
    let copyObj = Object.getOwnPropertyNames(originalObj).reduce(function (result, name) {
       
         result[name] = originalObj[name];
        
        return result;
    }, {});

    //get the getters values
    let prototype = Object.getPrototypeOf(originalObj);
     copyObj = Object.getOwnPropertyNames(prototype).reduce(function (result,name) {
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
        if (descriptor?.writable == null) {
            result[name] = originalObj[name];
        }
        return result;
    }, copyObj);
       
    return copyObj;
}
Gabriel H
  • 1,442
  • 2
  • 14
  • 33