1

I come back to this fabulous site to expose a behavior that I don't understand.

I create a json dynamically. I gradually add objects in a array. For that, I add firstly a "template". Then, in a second time, I modify the value of the object. But, this updating has been applied to all elements of the array that have been created with the same template. Whereas I expected that the updating modified only one object. I reproduced the problem to its minimum below:

thanks in advance

<!DOCTYPE html>
<html>
<head>
<title>Layers Control Tutorial - Leaflet</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
    .fa-test {
        width: 24px;
        height: 24px;
        background-repeat: no-repeat;
        background-image: url(data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJmaWxlLWRvd25sb2FkIiBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtZmlsZS1kb3dubG9hZCBmYS13LTEyIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDM4NCA1MTIiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIyNCAxMzZWMEgyNEMxMC43IDAgMCAxMC43IDAgMjR2NDY0YzAgMTMuMyAxMC43IDI0IDI0IDI0aDMzNmMxMy4zIDAgMjQtMTAuNyAyNC0yNFYxNjBIMjQ4Yy0xMy4yIDAtMjQtMTAuOC0yNC0yNHptNzYuNDUgMjExLjM2bC05Ni40MiA5NS43Yy02LjY1IDYuNjEtMTcuMzkgNi42MS0yNC4wNCAwbC05Ni40Mi05NS43QzczLjQyIDMzNy4yOSA4MC41NCAzMjAgOTQuODIgMzIwSDE2MHYtODBjMC04Ljg0IDcuMTYtMTYgMTYtMTZoMzJjOC44NCAwIDE2IDcuMTYgMTYgMTZ2ODBoNjUuMThjMTQuMjggMCAyMS40IDE3LjI5IDExLjI3IDI3LjM2ek0zNzcgMTA1TDI3OS4xIDdjLTQuNS00LjUtMTAuNi03LTE3LTdIMjU2djEyOGgxMjh2LTYuMWMwLTYuMy0yLjUtMTIuNC03LTE2Ljl6Ij48L3BhdGg+PC9zdmc+);
    }
</style>
</head>

<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>

<script>
var featureLayerJsonForRequest = {
    "CreateFeatureLayerRequest":{
                "themeId": "PTV_TruckAttributes",
                "features": [
                    {
                        "segments":[
                            {
                                "startNode": "string",
                                "endNode": "string",
                                "direction": "BOTH"
                            }
                        ],
                        "descriptions":[
                            {
                                "attributes":[
                                    {
                                        "key": "string",
                                        "value": "string"
                                    }
                                ],
                                "timeDomain": "string"
                            }
                        ]
                    }
                ]
    }
};

var templateDescription = {"attributes":[
                                    {
                                        "key": "string",
                                        "value": "string"
                                    }
                                ],
                                "timeDomain": "new"
                            };

// Before
console.log( featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions)

// Add a second description in the Json (with multiple way)
    //featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions.push(featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[0]) ;
     featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions.push(templateDescription) ;
    //featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[size] = templateDescription;
    //featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[size] = templateDescription;

// Add a third description in the json
     featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions.push(templateDescription);
     //featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions.push( featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[0]); // I would like do this

// Update the third element
     featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[2].timeDomain = "Updating value"; 

//After
console.log( featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions) // the second and the third element have the same value for the timeDomain Key


</script>
</body>
Cyrano
  • 13
  • 4

4 Answers4

1

The problem is that your template description is still one object. There are two references in the array, but they both point to the same object, so changing either one still changes the original object.

To do what you're trying to do, you need to make separate objects for each item in the array, before you mutate them. The easiest way to do that is to clone the original object. There's a few options for this. Personally, I would suggest using a library like lodash, and calling _.cloneDeep(templateDescription).

Tim Perry
  • 11,766
  • 1
  • 57
  • 85
0

When ever you push an object to array it will just push the reference of it so you are seeing that behavior like updating on all the items in your array. For that you can simply use

featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions.push(Object.assign(templateDescription)) ;

Still there is a problem as you have nested json you need to write custom logic to clone all the properties that are objects in your template. Simply you can use lodash cloneDeep for that. https://lodash.com/docs/#cloneDeep

Mr.Throg
  • 925
  • 6
  • 21
  • Lodash will work, but `Object.assign(template)` will not: it doesn't clone the object, or actually do anything at all. You could use `Object.assign({}, template` to do a _shallow_ clone, but really you need some kind of deep cloning. – Tim Perry Jul 31 '19 at 07:57
  • The simplest (not sure if the best performance-wise) way to clone would be simple `const copy = JSON.parse(JSON.stringify(original))`. – mbojko Jul 31 '19 at 08:17
  • @VenkatLokeswar why do you call `Object.assign()` in your example? My point is that that does nothing, it doesn't clone. `Object.assign(x) === x`, for any object `x`. Without that your example is the same as the question, and has the same problem. – Tim Perry Jul 31 '19 at 12:10
0

When you are pushing an object, it's gonna push it as reference. You just need to make sure you clone the current object before you push it:

var featureLayerJsonForRequest = {
    "CreateFeatureLayerRequest":{
                "themeId": "PTV_TruckAttributes",
                "features": [
                    {
                        "segments":[
                            {
                                "startNode": "string",
                                "endNode": "string",
                                "direction": "BOTH"
                            }
                        ],
                        "descriptions":[
                            {
                                "attributes":[
                                    {
                                        "key": "string",
                                        "value": "string"
                                    }
                                ],
                                "timeDomain": "string"
                            }
                        ]
                    }
                ]
    }
};

var templateDescription = {
              "attributes":[
                                    {
                                        "key": "string",
                                        "value": "string"
                                    }
                                ],
                                "timeDomain": "new"
                            };

// Add a second description in the Json
featureLayerJsonForRequest
 .CreateFeatureLayerRequest
  .features[0].descriptions
   .push(JSON.parse(JSON.stringify(templateDescription))); // Cloning the var into a new JSON

// Add a third description in the json     
featureLayerJsonForRequest
 .CreateFeatureLayerRequest
  .features[0].descriptions
   .push(JSON.parse(JSON.stringify(templateDescription))); // Cloning the var into a new JSON

// Update the third element
featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[2].timeDomain = "Updating value"; 

//print TEMPLATE "timeDomain"
document.write(JSON.stringify(featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[0].timeDomain))

//print SECOND item "timeDomain"
document.write(JSON.stringify(featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[1].timeDomain))

//print THIRD item "timeDomain"
document.write(JSON.stringify(featureLayerJsonForRequest.CreateFeatureLayerRequest.features[0].descriptions[2].timeDomain))

The code above works, just cloned the var "templateDescription" into a new JSON before pushing it into the array.

Leo Lenori
  • 11
  • 3
0

I use the solution Object.assign({}, template) to avoid add an extra library and it seems to works well.

Thanks a lot for your explanations and for yours speeds :-)

Cyrano
  • 13
  • 4
  • Be slightly careful with this, because `Object.assign({}, ...)` is a [shallow clone](https://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy) - it only clones the top level of the object, and the rest is still shared. That means that you can now write to `description.timeDomain` and set a different result for each description safely, but if you call `description.attributes.push(...)` it will push into _every_ description. – Tim Perry Jul 31 '19 at 12:14
  • You have advanced my problem. Indeed, I also want to modify the child elements of the descriptions in an individual way. Do you have a solution for me? – Cyrano Jul 31 '19 at 13:44
  • https://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript can show you most of the options to do it by hand. The easiest is `JSON.parse(JSON.stringify(x))`, and that will work fine for your example here. In general though it's a little slow, and doesn't support many types properly. I would use a utility library - this is built in to lodash/jquery/underscore and many others, or there are tiny specific libraries too: https://www.npmjs.com/package/clone – Tim Perry Jul 31 '19 at 16:19