-2

I have a object like so:

$scope.query = {
    filter: {
        column: {
            productName: 'Some Product',
            price: 29.95,
            ...
        },
        table: {
            productType: 'GM',
            categoryId: 1,
            ...
        }
    }
};

How do I get a string that represents the whole object in dot notation? e.g.

query.filter.table.productType

To clarify, I am using this string value as a key to store a key/value pair in localStorage.

I am using angular to $wacth each property on the object for a change. Since you can't watch an object and know which property changed with watching all, I need to get creative and store each property in a key/value pair.

Sam
  • 15,336
  • 25
  • 85
  • 148
  • 4
    How would the string look like if the object had multiple properties? How does this string represent your object when it misses the `GM` property value? – Bergi Jun 23 '14 at 19:01
  • Aside from Bergi's note, as it sits, his seems like a pretty simple task. So what's the problem? – cookie monster Jun 23 '14 at 19:03
  • 2
    Usually when people talk about a string representation of a JavaScript object, they're talking about JSON. Are you specifically saying you DON'T want to use JSON? Edit: and if you *don't* want JSON, why not? This feels like you might have an XY problem (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – aquinas Jun 23 '14 at 19:03
  • 1
    do you want an array of all paths, or just one specific one? if it's one specific, you can iterate through to find matches, but note that alike primitives could trigger false hits. – dandavis Jun 23 '14 at 19:05
  • I just need a string of the object graph. I am looking to turn that into `'query.filter.table.productType'` – Sam Jun 23 '14 at 19:05
  • *Why* do you need a string of the object graph? What are you going to do with it? And as @Bergi said, what if there are multiple properties? – aquinas Jun 23 '14 at 19:06
  • @aquinas - I am using it as a key to store a key/value pair in localStorage. – Sam Jun 23 '14 at 19:07
  • @Sam its better to store the object as string and then retrieve it parse the json – juvian Jun 23 '14 at 19:08
  • why not just use a string instead of deep objects with only one property per sub-object? – dandavis Jun 23 '14 at 19:09
  • If you're using it as a key, surely you must KNOW what the key is right? This seems like you should have ONE key, and the object stored should be the object itself (in JSON). Storing the properties separately seems like a really weird and bad idea. – aquinas Jun 23 '14 at 19:09
  • re: updated question: there is no reflection in JS, but you can use getters and setters or WeakMaps to do what you want. – dandavis Jun 23 '14 at 19:11
  • @aquinas I am derviving the key based on the object hierarchy. I dont know the key until I do that. – Sam Jun 23 '14 at 19:11
  • 1
    @Sam: ok, but how do you know which property to get the path for without knowing the path? – dandavis Jun 23 '14 at 19:12
  • I iterate over each property in the object. And then using angular put a $watch on each property. – Sam Jun 23 '14 at 19:13
  • OK, well, just push each property onto a stack or something as you traverse. *But you are doing the wrong thing.* Everyone here is trying to point you to THIS solution: http://stackoverflow.com/questions/2010892/storing-objects-in-html5-localstorage. Trust me, storing each property does NOT make sense. You're a .NET guy, you would expect a dictionary right? Not a Dictionary where each key is a property of the original object right? – aquinas Jun 23 '14 at 19:15
  • Have a look at the possible duplicate [way to flatten/unflatten nested JSON object](http://stackoverflow.com/q/19098797/1048572) – Bergi Jun 23 '14 at 19:16
  • @aquinas - I have to `$watch` each property individually as Angular does not provide the object changing in the watch, only the new and old value. – Sam Jun 23 '14 at 19:18
  • I'm not an angular guy but I *cannot* believe that you can't be notified when an object changes, or get the object that a property belongs to. If that were the case, that would be completely horrible. – aquinas Jun 23 '14 at 19:25
  • @Sam: if angular doesn't provide the object to the watch callback, you can still define the watch inside a function that has a var to the object/subobject, and closure that var in the watch callback. – dandavis Jun 23 '14 at 19:25

3 Answers3

1

You can do it recursively, and produces "key" in an array.

var obj = {
    query: {
        filter: {
            table: {
                productType: 'GM'
            }
        }
    }
};

var stringify = function (e) {
    var rs = [];
    for (var k in e) {
        if (e.hasOwnProperty(k)) {
            if (typeof e[k] == 'object') {
                var l = stringify(e[k]);
                for (var i = 0; i < l.length; i++) {
                    rs.push(k + '.' + l[i]);
                }
            } else {
                rs.push(k);
            }
        }
    }
    return rs;
}

console.log(stringify(obj));

outputs:

["query.filter.table.productType"] 

fiddle

Fabricator
  • 12,722
  • 2
  • 27
  • 40
  • on OP data, we get ["filter.column.productName", "filter.column.price", "filter.table.productType", "filter.table.categoryId"]. that's correct and cool. not sure it helps OP, but not sure if OP can be helped in the way requested anyway... – dandavis Jun 23 '14 at 19:38
  • @dandavis isnt that what the OP wanted? – Fabricator Jun 23 '14 at 19:45
  • @Fabrictor: that's what i'd like to know. it's not clear how a "Table of contents" like the one produced by your awesome code helps angular identify one primitive value from the next, but i think given the info we have, this is a good answer. – dandavis Jun 23 '14 at 19:50
1

Demo

Before Ques Edit

var $scope = {
    query: {
        filter: {
            table: {
                productType: 'GM'
            }
        }
    }
};

var k = JSON.stringify($scope)
//output "{"query":{"filter":{"table":{"productType":"GM"}}}}"

k.match(/\w+(?=\"\:)/g).join('.')
//output"query.filter.table.productType"

Edit

Updated Demo

If OP has no issue with the position of child elements

var $scope = {}
$scope.query = {
    filter: {
        column: {
            productName: 'Some Product',
            price: 29.95
        },
        table: {
            productType: 'GM',
            categoryId: 1,
        }
    }
};

k=JSON.stringify($scope)
{"query":{"filter":{"column":{"productName":"Some Product","price":29.95},"table":{"productType":"GM","categoryId":1}}}}

k.match(/\w+(?=\"\:)/g).join('.')
"query.filter.column.productName.price.table.productType.categoryId"
Harpreet Singh
  • 2,651
  • 21
  • 31
0

By iterating the properties into an array recursively you could create a hierarchical structure that represents the data in the object. From here you could parse the results out as you wish.

var scope = {
    query: {
        filter: {
            column: {
                productName: 'Some Product',
                price: 29.95
            },
            table: {
                productType: 'GM',
                categoryId: 1
            }
        }
    }
};    
function buildProps(subject) {
    var result = [];
    for (var key in subject) {
        if (subject.hasOwnProperty(key)) {
            if (typeof subject[key] == "object") {
                result.push(key, buildProps(subject[key]));
            } else {
                result.push(key);
            }
        }
    }
    return result;
}    
function stringify(input) {
    var result = [];
    for (var i = 0; i < input.length; i++) {
        if (typeof input[i] == "string") {
            result.push(input[i]);
        } else {
            result = result.concat(stringify(input[i]));
        }
    }
    return result.join('.');
}

console.log(buildProps(scope));
console.log(stringify(buildProps(scope)));

Parse out the strings in the resulting array/sub-arrays, format it any way you like.

In my simple example I just list them in order: query.filter.column.productName.price.table.productType.categoryId

NickSuperb
  • 1,174
  • 1
  • 8
  • 28