Safely setting object properties with dot notation strings in JavaScript

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite












What my function does:



Safely sets object properties with dot notation strings in JavaScript.



That is to say, it allows setting a nested property on an object using a string such as "a.b.c".



For example: set(obj, "a.b.c", value) would be equivalent to a.b.c = value



Some notes on intended behavior



  • When setting a property at a given path, if any portion of that path doesn't exist, it should be created

  • When creating a path portion, if the next key in the path is NOT an integer, the current portion should be created as an object

  • When creating a path portion, if the next key in the path is an integer, the current portion should be created as an array

Why?



I'd like to be able to set deeply nested properties on an object in an environment where I can't be sure that all the properties in the path will always exist and I don't want to deal with the logic of checking for and setting each key manually in more than one place. This allows me to quickly set the needed value without caring if the keys were present previously or not.






/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);









share|improve this question















  • 1




    Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
    – iabw
    May 14 at 15:03
















up vote
6
down vote

favorite












What my function does:



Safely sets object properties with dot notation strings in JavaScript.



That is to say, it allows setting a nested property on an object using a string such as "a.b.c".



For example: set(obj, "a.b.c", value) would be equivalent to a.b.c = value



Some notes on intended behavior



  • When setting a property at a given path, if any portion of that path doesn't exist, it should be created

  • When creating a path portion, if the next key in the path is NOT an integer, the current portion should be created as an object

  • When creating a path portion, if the next key in the path is an integer, the current portion should be created as an array

Why?



I'd like to be able to set deeply nested properties on an object in an environment where I can't be sure that all the properties in the path will always exist and I don't want to deal with the logic of checking for and setting each key manually in more than one place. This allows me to quickly set the needed value without caring if the keys were present previously or not.






/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);









share|improve this question















  • 1




    Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
    – iabw
    May 14 at 15:03












up vote
6
down vote

favorite









up vote
6
down vote

favorite











What my function does:



Safely sets object properties with dot notation strings in JavaScript.



That is to say, it allows setting a nested property on an object using a string such as "a.b.c".



For example: set(obj, "a.b.c", value) would be equivalent to a.b.c = value



Some notes on intended behavior



  • When setting a property at a given path, if any portion of that path doesn't exist, it should be created

  • When creating a path portion, if the next key in the path is NOT an integer, the current portion should be created as an object

  • When creating a path portion, if the next key in the path is an integer, the current portion should be created as an array

Why?



I'd like to be able to set deeply nested properties on an object in an environment where I can't be sure that all the properties in the path will always exist and I don't want to deal with the logic of checking for and setting each key manually in more than one place. This allows me to quickly set the needed value without caring if the keys were present previously or not.






/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);









share|improve this question











What my function does:



Safely sets object properties with dot notation strings in JavaScript.



That is to say, it allows setting a nested property on an object using a string such as "a.b.c".



For example: set(obj, "a.b.c", value) would be equivalent to a.b.c = value



Some notes on intended behavior



  • When setting a property at a given path, if any portion of that path doesn't exist, it should be created

  • When creating a path portion, if the next key in the path is NOT an integer, the current portion should be created as an object

  • When creating a path portion, if the next key in the path is an integer, the current portion should be created as an array

Why?



I'd like to be able to set deeply nested properties on an object in an environment where I can't be sure that all the properties in the path will always exist and I don't want to deal with the logic of checking for and setting each key manually in more than one place. This allows me to quickly set the needed value without caring if the keys were present previously or not.






/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);








/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);





/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param object obj The object on which to set the given value
* @param string path The dot notation path to the nested property where the value should be set
* @param mixed value The value that should be set
* @return mixed
*
*/
function set(obj, path, value)
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : ;
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++)
// get the current path part
var key = keys[i];

// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key))[1-9]d*)$/.test(nextKey);
curStep[key] = useArray ? : ;

// update curStep to point to the new level
curStep = curStep[key];

// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
;



/** Usage **/

console.log('setting non existant a.b.c to "some value":');
var test = ;
set(test, 'a.b.c', 'some value');
console.log(test);

console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);

console.log('setting non existant a.b.0 to "some value":');
var test = ;
set(test, 'a.b.0', 'some value');
console.log(test);

console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);








share|improve this question










share|improve this question




share|improve this question









asked May 14 at 3:35









DelightedD0D

1607




1607







  • 1




    Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
    – iabw
    May 14 at 15:03












  • 1




    Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
    – iabw
    May 14 at 15:03







1




1




Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
– iabw
May 14 at 15:03




Just in case you're not aware of it, Dottie may have considered some of the issues raised below - github.com/mickhansen/dottie.js
– iabw
May 14 at 15:03










2 Answers
2






active

oldest

votes

















up vote
5
down vote



accepted










Dangerous



There are so many caveats in this function I would really not allow it in any code base because of potencial unexpected , or misunderstood behaviour. Its just waiting to cause problems in completely unrelated code.



Some points.



  • Some variables should be const they are keys, key, useArray, nextKey, finalStep


  • Don't see why you create a new variable curStep (with very odd name) to hold the current object. Just reuse obj


  • Avoid the name set as it is used as a JavaScript token eg create a setter set val(blah) ... Maybe the name could be assignToPath


  • null is also of type "object" so it will pay to extend the test on the first line to include the null Would be best to throw


  • You currently return undefined. Maybe it would be more helpful to return the object you assigned the new property to.


  • Why use the long version !Object.prototype.hasOwnProperty.call(curStep, key) ?? when !curStep.hasOwnProperty(key) will do the same.


  • If a property is expressly set to undefined your function fails.


const test = a: undefined 
assignToPath(test, "a.b", "foo" ); //throws but would expect test.a.b = "foo"


  • Objects can be locked via various Object functions Object.freeze, Object.seal, Object.preventExtensions, Object.defineProperties and Object.defineProperty Your function ignores these settings.


  • If a property exists but is not an Object or Array you still attempt to add a property to it.


const test = a: "blah" 
assignToPath(test, "a.b", "foo" ); // fails


  • This is what I would consider a low level function and as such would be one of the few that need to throw errors rather than fail silently. It should throw at anything that is not as expected. For example if the object to assign a property to is an array, or an object

const test = a: 
assignToPath(test, "a.b", "foo" ); // Maybe this should throw

const test = a:
assignToPath(test, "a.1", "foo" ); // This should throw as it is unclear what the result should be.
// the case against the above
const test =
assignToPath(test, "a.1", "foo" ); // creates an array


Suggestions only.



Adding a settings argument would allow for better control of the behaviour when things get a little ambiguous.



assignToPath(a:, "a.b", "bar", arrayHaveProps : true);

assignToPath(a:, "a.1", "bar", allowObjIndex : true);


You can shorten the code if you use a while loop and shift the path as you go.



// simple example of using `while` and `shift` rather than a `for` loop
var obj = a:b:c:"a";
const path = "a.b.c".split(".");
while(path.length > 1)
obj = obj[path.shift()];

obj[path.shift()] = "b";





share|improve this answer





















  • I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
    – sineemore
    May 14 at 8:00










  • or even Object.create(null).hasOwnProperty()
    – sineemore
    May 14 at 8:02










  • @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
    – Blindman67
    May 14 at 9:13

















up vote
7
down vote













obj = typeof obj === 'object' ? obj : ;


When obj argument points to something other than object you will create one. Like in set("foo", "a.b.c", "Yay!"). I guess you want to return it at function end or it will be lost.



Other notes:




  • /^+?(0|[1-9]d*)$/ allows + in integer key inputs

  • you may use isNaN(+nextKey) instead of regular expression, but beware float or signed numbers


  • finalStep is a bit misleading name. You may use key = keys[keys.length - 1] instead.





share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194338%2fsafely-setting-object-properties-with-dot-notation-strings-in-javascript%23new-answer', 'question_page');

    );

    Post as a guest






























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    5
    down vote



    accepted










    Dangerous



    There are so many caveats in this function I would really not allow it in any code base because of potencial unexpected , or misunderstood behaviour. Its just waiting to cause problems in completely unrelated code.



    Some points.



    • Some variables should be const they are keys, key, useArray, nextKey, finalStep


    • Don't see why you create a new variable curStep (with very odd name) to hold the current object. Just reuse obj


    • Avoid the name set as it is used as a JavaScript token eg create a setter set val(blah) ... Maybe the name could be assignToPath


    • null is also of type "object" so it will pay to extend the test on the first line to include the null Would be best to throw


    • You currently return undefined. Maybe it would be more helpful to return the object you assigned the new property to.


    • Why use the long version !Object.prototype.hasOwnProperty.call(curStep, key) ?? when !curStep.hasOwnProperty(key) will do the same.


    • If a property is expressly set to undefined your function fails.


    const test = a: undefined 
    assignToPath(test, "a.b", "foo" ); //throws but would expect test.a.b = "foo"


    • Objects can be locked via various Object functions Object.freeze, Object.seal, Object.preventExtensions, Object.defineProperties and Object.defineProperty Your function ignores these settings.


    • If a property exists but is not an Object or Array you still attempt to add a property to it.


    const test = a: "blah" 
    assignToPath(test, "a.b", "foo" ); // fails


    • This is what I would consider a low level function and as such would be one of the few that need to throw errors rather than fail silently. It should throw at anything that is not as expected. For example if the object to assign a property to is an array, or an object

    const test = a: 
    assignToPath(test, "a.b", "foo" ); // Maybe this should throw

    const test = a:
    assignToPath(test, "a.1", "foo" ); // This should throw as it is unclear what the result should be.
    // the case against the above
    const test =
    assignToPath(test, "a.1", "foo" ); // creates an array


    Suggestions only.



    Adding a settings argument would allow for better control of the behaviour when things get a little ambiguous.



    assignToPath(a:, "a.b", "bar", arrayHaveProps : true);

    assignToPath(a:, "a.1", "bar", allowObjIndex : true);


    You can shorten the code if you use a while loop and shift the path as you go.



    // simple example of using `while` and `shift` rather than a `for` loop
    var obj = a:b:c:"a";
    const path = "a.b.c".split(".");
    while(path.length > 1)
    obj = obj[path.shift()];

    obj[path.shift()] = "b";





    share|improve this answer





















    • I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
      – sineemore
      May 14 at 8:00










    • or even Object.create(null).hasOwnProperty()
      – sineemore
      May 14 at 8:02










    • @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
      – Blindman67
      May 14 at 9:13














    up vote
    5
    down vote



    accepted










    Dangerous



    There are so many caveats in this function I would really not allow it in any code base because of potencial unexpected , or misunderstood behaviour. Its just waiting to cause problems in completely unrelated code.



    Some points.



    • Some variables should be const they are keys, key, useArray, nextKey, finalStep


    • Don't see why you create a new variable curStep (with very odd name) to hold the current object. Just reuse obj


    • Avoid the name set as it is used as a JavaScript token eg create a setter set val(blah) ... Maybe the name could be assignToPath


    • null is also of type "object" so it will pay to extend the test on the first line to include the null Would be best to throw


    • You currently return undefined. Maybe it would be more helpful to return the object you assigned the new property to.


    • Why use the long version !Object.prototype.hasOwnProperty.call(curStep, key) ?? when !curStep.hasOwnProperty(key) will do the same.


    • If a property is expressly set to undefined your function fails.


    const test = a: undefined 
    assignToPath(test, "a.b", "foo" ); //throws but would expect test.a.b = "foo"


    • Objects can be locked via various Object functions Object.freeze, Object.seal, Object.preventExtensions, Object.defineProperties and Object.defineProperty Your function ignores these settings.


    • If a property exists but is not an Object or Array you still attempt to add a property to it.


    const test = a: "blah" 
    assignToPath(test, "a.b", "foo" ); // fails


    • This is what I would consider a low level function and as such would be one of the few that need to throw errors rather than fail silently. It should throw at anything that is not as expected. For example if the object to assign a property to is an array, or an object

    const test = a: 
    assignToPath(test, "a.b", "foo" ); // Maybe this should throw

    const test = a:
    assignToPath(test, "a.1", "foo" ); // This should throw as it is unclear what the result should be.
    // the case against the above
    const test =
    assignToPath(test, "a.1", "foo" ); // creates an array


    Suggestions only.



    Adding a settings argument would allow for better control of the behaviour when things get a little ambiguous.



    assignToPath(a:, "a.b", "bar", arrayHaveProps : true);

    assignToPath(a:, "a.1", "bar", allowObjIndex : true);


    You can shorten the code if you use a while loop and shift the path as you go.



    // simple example of using `while` and `shift` rather than a `for` loop
    var obj = a:b:c:"a";
    const path = "a.b.c".split(".");
    while(path.length > 1)
    obj = obj[path.shift()];

    obj[path.shift()] = "b";





    share|improve this answer





















    • I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
      – sineemore
      May 14 at 8:00










    • or even Object.create(null).hasOwnProperty()
      – sineemore
      May 14 at 8:02










    • @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
      – Blindman67
      May 14 at 9:13












    up vote
    5
    down vote



    accepted







    up vote
    5
    down vote



    accepted






    Dangerous



    There are so many caveats in this function I would really not allow it in any code base because of potencial unexpected , or misunderstood behaviour. Its just waiting to cause problems in completely unrelated code.



    Some points.



    • Some variables should be const they are keys, key, useArray, nextKey, finalStep


    • Don't see why you create a new variable curStep (with very odd name) to hold the current object. Just reuse obj


    • Avoid the name set as it is used as a JavaScript token eg create a setter set val(blah) ... Maybe the name could be assignToPath


    • null is also of type "object" so it will pay to extend the test on the first line to include the null Would be best to throw


    • You currently return undefined. Maybe it would be more helpful to return the object you assigned the new property to.


    • Why use the long version !Object.prototype.hasOwnProperty.call(curStep, key) ?? when !curStep.hasOwnProperty(key) will do the same.


    • If a property is expressly set to undefined your function fails.


    const test = a: undefined 
    assignToPath(test, "a.b", "foo" ); //throws but would expect test.a.b = "foo"


    • Objects can be locked via various Object functions Object.freeze, Object.seal, Object.preventExtensions, Object.defineProperties and Object.defineProperty Your function ignores these settings.


    • If a property exists but is not an Object or Array you still attempt to add a property to it.


    const test = a: "blah" 
    assignToPath(test, "a.b", "foo" ); // fails


    • This is what I would consider a low level function and as such would be one of the few that need to throw errors rather than fail silently. It should throw at anything that is not as expected. For example if the object to assign a property to is an array, or an object

    const test = a: 
    assignToPath(test, "a.b", "foo" ); // Maybe this should throw

    const test = a:
    assignToPath(test, "a.1", "foo" ); // This should throw as it is unclear what the result should be.
    // the case against the above
    const test =
    assignToPath(test, "a.1", "foo" ); // creates an array


    Suggestions only.



    Adding a settings argument would allow for better control of the behaviour when things get a little ambiguous.



    assignToPath(a:, "a.b", "bar", arrayHaveProps : true);

    assignToPath(a:, "a.1", "bar", allowObjIndex : true);


    You can shorten the code if you use a while loop and shift the path as you go.



    // simple example of using `while` and `shift` rather than a `for` loop
    var obj = a:b:c:"a";
    const path = "a.b.c".split(".");
    while(path.length > 1)
    obj = obj[path.shift()];

    obj[path.shift()] = "b";





    share|improve this answer













    Dangerous



    There are so many caveats in this function I would really not allow it in any code base because of potencial unexpected , or misunderstood behaviour. Its just waiting to cause problems in completely unrelated code.



    Some points.



    • Some variables should be const they are keys, key, useArray, nextKey, finalStep


    • Don't see why you create a new variable curStep (with very odd name) to hold the current object. Just reuse obj


    • Avoid the name set as it is used as a JavaScript token eg create a setter set val(blah) ... Maybe the name could be assignToPath


    • null is also of type "object" so it will pay to extend the test on the first line to include the null Would be best to throw


    • You currently return undefined. Maybe it would be more helpful to return the object you assigned the new property to.


    • Why use the long version !Object.prototype.hasOwnProperty.call(curStep, key) ?? when !curStep.hasOwnProperty(key) will do the same.


    • If a property is expressly set to undefined your function fails.


    const test = a: undefined 
    assignToPath(test, "a.b", "foo" ); //throws but would expect test.a.b = "foo"


    • Objects can be locked via various Object functions Object.freeze, Object.seal, Object.preventExtensions, Object.defineProperties and Object.defineProperty Your function ignores these settings.


    • If a property exists but is not an Object or Array you still attempt to add a property to it.


    const test = a: "blah" 
    assignToPath(test, "a.b", "foo" ); // fails


    • This is what I would consider a low level function and as such would be one of the few that need to throw errors rather than fail silently. It should throw at anything that is not as expected. For example if the object to assign a property to is an array, or an object

    const test = a: 
    assignToPath(test, "a.b", "foo" ); // Maybe this should throw

    const test = a:
    assignToPath(test, "a.1", "foo" ); // This should throw as it is unclear what the result should be.
    // the case against the above
    const test =
    assignToPath(test, "a.1", "foo" ); // creates an array


    Suggestions only.



    Adding a settings argument would allow for better control of the behaviour when things get a little ambiguous.



    assignToPath(a:, "a.b", "bar", arrayHaveProps : true);

    assignToPath(a:, "a.1", "bar", allowObjIndex : true);


    You can shorten the code if you use a while loop and shift the path as you go.



    // simple example of using `while` and `shift` rather than a `for` loop
    var obj = a:b:c:"a";
    const path = "a.b.c".split(".");
    while(path.length > 1)
    obj = obj[path.shift()];

    obj[path.shift()] = "b";






    share|improve this answer













    share|improve this answer



    share|improve this answer











    answered May 14 at 7:31









    Blindman67

    5,2961320




    5,2961320











    • I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
      – sineemore
      May 14 at 8:00










    • or even Object.create(null).hasOwnProperty()
      – sineemore
      May 14 at 8:02










    • @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
      – Blindman67
      May 14 at 9:13
















    • I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
      – sineemore
      May 14 at 8:00










    • or even Object.create(null).hasOwnProperty()
      – sineemore
      May 14 at 8:02










    • @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
      – Blindman67
      May 14 at 9:13















    I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
    – sineemore
    May 14 at 8:00




    I guess set(, "hasOwnProperty", null) is a reason to use Object.prototype.hasOwnProperty
    – sineemore
    May 14 at 8:00












    or even Object.create(null).hasOwnProperty()
    – sineemore
    May 14 at 8:02




    or even Object.create(null).hasOwnProperty()
    – sineemore
    May 14 at 8:02












    @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
    – Blindman67
    May 14 at 9:13




    @sineemore Your examples are not reason to use the indirect call. How about Object.prototype.hasOwnProperty =()=> Now what???
    – Blindman67
    May 14 at 9:13












    up vote
    7
    down vote













    obj = typeof obj === 'object' ? obj : ;


    When obj argument points to something other than object you will create one. Like in set("foo", "a.b.c", "Yay!"). I guess you want to return it at function end or it will be lost.



    Other notes:




    • /^+?(0|[1-9]d*)$/ allows + in integer key inputs

    • you may use isNaN(+nextKey) instead of regular expression, but beware float or signed numbers


    • finalStep is a bit misleading name. You may use key = keys[keys.length - 1] instead.





    share|improve this answer

























      up vote
      7
      down vote













      obj = typeof obj === 'object' ? obj : ;


      When obj argument points to something other than object you will create one. Like in set("foo", "a.b.c", "Yay!"). I guess you want to return it at function end or it will be lost.



      Other notes:




      • /^+?(0|[1-9]d*)$/ allows + in integer key inputs

      • you may use isNaN(+nextKey) instead of regular expression, but beware float or signed numbers


      • finalStep is a bit misleading name. You may use key = keys[keys.length - 1] instead.





      share|improve this answer























        up vote
        7
        down vote










        up vote
        7
        down vote









        obj = typeof obj === 'object' ? obj : ;


        When obj argument points to something other than object you will create one. Like in set("foo", "a.b.c", "Yay!"). I guess you want to return it at function end or it will be lost.



        Other notes:




        • /^+?(0|[1-9]d*)$/ allows + in integer key inputs

        • you may use isNaN(+nextKey) instead of regular expression, but beware float or signed numbers


        • finalStep is a bit misleading name. You may use key = keys[keys.length - 1] instead.





        share|improve this answer













        obj = typeof obj === 'object' ? obj : ;


        When obj argument points to something other than object you will create one. Like in set("foo", "a.b.c", "Yay!"). I guess you want to return it at function end or it will be lost.



        Other notes:




        • /^+?(0|[1-9]d*)$/ allows + in integer key inputs

        • you may use isNaN(+nextKey) instead of regular expression, but beware float or signed numbers


        • finalStep is a bit misleading name. You may use key = keys[keys.length - 1] instead.






        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered May 14 at 7:47









        sineemore

        1,193217




        1,193217






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194338%2fsafely-setting-object-properties-with-dot-notation-strings-in-javascript%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Chat program with C++ and SFML

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            Will my employers contract hold up in court?