Good day all,
I am looking create a JSON script that will allow objects to either fetch a value or continue down a data path
for example:
{
"A" : {
"B" : "123"
}
}
In the example above if i get the value of A.B, i get the result of 123. However I also want A to have their own value of for example "XYZ".
So we I say, what is A? It will return "XYZ" and I also say, What is A.B it will return "123"
I hope this makes sense, JSON seems to follow a single path, but i want the ability to follow the path and assign values to each node. its like the node has a value but it also has children and a parent.
Any help regarding theory is greatly appreciated
You can create custom function using reduce() method to be able to pass nested keys as a string.
const data = {"A": {"B": "123"}}
const getPath = (path, obj) => {
return path.split('.').reduce(function(r, e, i, arr) {
return r[e] || (arr[i + 1] ? {} : undefined)
}, obj)
}
console.log(getPath('A.B', data))
console.log(getPath('A', data))
console.log(getPath('A.C', data))
If you want to always return last found value in path you can use this instead.
const data = {
"A": {
"B": "123",
"C": {"D": 2}
}
}
const getPath = (path, obj) => {
return path.split('.').reduce((r, e) => (r[e] || r ), obj)
}
console.log(getPath('A.B', data))
console.log(getPath('A.C.F.G', data))
console.log(getPath('A.B.Q', data))
You cannot assign 2 different values to an object property. You could do something like this however:
{
"A" : {
"B" : "123",
"value" : "XYZ"
}
}
Referencing A.value will return XYZ and A.B will return 123
Edit:
If you make A a function that returns the value you want, you can then assign properties to A. But this is probably a bad practice:
let A = function () {
return 'XYZ';
}
A.B = '123';
let obj = {
A
};
console.log(A()); // XYZ
console.log(A.B); // 123
JSON stands for JavaScript Object Notation and this means a JSON represents an object. Each attribute of an object may only have one value. In your case this is the following:
var a = { "A": { "B": "123"}
this represents an object like { A: { B: 123} and this means:
console.log(a.A) // {B: 123}
console.log(a.A.B) // 123
so what you are trying to do is just not possible.
https://www.w3schools.com/js/js_json_intro.asp
I am not sure I understand your problem, but it seems like the logic to figure out the JSON string you are looking for should be figured out before the request or by the response. Returning multiple paths of logic doesn’t seem like a good idea.
Related
I would like to take text that I generated and stored in a string and use it like a template literal.
var generatedText = "Pretend this text was generated and then stored in a variable. ";
generatedText = "But I still need to use it as a template it to get ${variable}.";
var variable = "Successs!!!!";
console.log(generatedText);
//prints 'But I still need to interpolate it to get ${variable}.'
//how can I make it print using variable in it like a template as if it were doing this
console.log(`But I still need to use it as a template it to get ${variable}.`);
//prints 'But I still need to use it as a template it to get Successs!!!!.'
How can I get generated text to become a template string?
generatedText must start in a variable so I need to find a way to convert it to a template string if possible.
Edit:
I didn't think I would have to put this but also I don't want to use eval to risk evaluating random code...
For the general situation, you can use a replacer function to replace every occurrence of ${someProp} with the someProp property on an object:
const interpolate = (str, obj) => str.replace(
/\${([^}]+)}/g,
(_, prop) => obj[prop]
);
const generatedText = "But I still need to use it as a template it to get ${variable}.";
const variable = "Successs!!!!";
console.log(interpolate(generatedText, { variable }));
The regular expression \${([^}]+)} means:
\$ - Literal $
{ - Literal {
([^}]+) First (and only) capture group:
[^}]+ - One or more characters which are not }
} - Literal }
Since prop is the property name found in between the brackets, replace with obj[prop] to replace with the desired replacement.
The interpolate function below is an extended version of this answer that adds support for simple nested object field references (e.g.: a.b.c)
function interpolate(s, obj) {
return s.replace(/[$]{([^}]+)}/g, function(_, path) {
const properties = path.split('.');
return properties.reduce((prev, curr) => prev && prev[curr], obj);
})
}
console.log(interpolate('hello ${a.b.c}', {a: {b: {c: 'world'}}}));
// Displays 'hello world'
The interpolate function below is an extended version of the above solution adds support for simple nested object field references, with addition of arrays (e.g.: a[0][2].b.c)
const interpolate = (str, obj) => {
return str.replace(/\${([^}]+)}/g, (_, target) => {
let keys = target.split(".");
return keys.reduce((prev, curr) => {
if (curr.search(/\[/g) > -1) {
//if element/key in target array is array, get the value and return
let m_curr = curr.replace(/\]/g, "");
let arr = m_curr.split("[");
return arr.reduce((pr, cu) => {
return pr && pr[cu];
}, prev);
} else {
//else it is a object, get the value and return
return prev && prev[curr];
}
}, obj);
});
};
let template = "hello ${a[0][0].b.c}";
let data = {
a: [
[{
b: {
c: "world",
f: "greetings"
}
}, 2], 3
],
d: 12,
e: 14
}
console.log(interpolate(template, { ...data
}));
You should emulate a template literal instead, because letting text from ~somewhere~ run arbitrary JavaScript like a real template literal’s ${} sections can usually isn’t a good idea:
generatedText.replace(/\$\{variable}/g, variable);
I would like to take text that I generated and stored in a string and use it like a template literal.
var generatedText = "Pretend this text was generated and then stored in a variable. ";
generatedText = "But I still need to use it as a template it to get ${variable}.";
var variable = "Successs!!!!";
console.log(generatedText);
//prints 'But I still need to interpolate it to get ${variable}.'
//how can I make it print using variable in it like a template as if it were doing this
console.log(`But I still need to use it as a template it to get ${variable}.`);
//prints 'But I still need to use it as a template it to get Successs!!!!.'
How can I get generated text to become a template string?
generatedText must start in a variable so I need to find a way to convert it to a template string if possible.
Edit:
I didn't think I would have to put this but also I don't want to use eval to risk evaluating random code...
For the general situation, you can use a replacer function to replace every occurrence of ${someProp} with the someProp property on an object:
const interpolate = (str, obj) => str.replace(
/\${([^}]+)}/g,
(_, prop) => obj[prop]
);
const generatedText = "But I still need to use it as a template it to get ${variable}.";
const variable = "Successs!!!!";
console.log(interpolate(generatedText, { variable }));
The regular expression \${([^}]+)} means:
\$ - Literal $
{ - Literal {
([^}]+) First (and only) capture group:
[^}]+ - One or more characters which are not }
} - Literal }
Since prop is the property name found in between the brackets, replace with obj[prop] to replace with the desired replacement.
The interpolate function below is an extended version of this answer that adds support for simple nested object field references (e.g.: a.b.c)
function interpolate(s, obj) {
return s.replace(/[$]{([^}]+)}/g, function(_, path) {
const properties = path.split('.');
return properties.reduce((prev, curr) => prev && prev[curr], obj);
})
}
console.log(interpolate('hello ${a.b.c}', {a: {b: {c: 'world'}}}));
// Displays 'hello world'
The interpolate function below is an extended version of the above solution adds support for simple nested object field references, with addition of arrays (e.g.: a[0][2].b.c)
const interpolate = (str, obj) => {
return str.replace(/\${([^}]+)}/g, (_, target) => {
let keys = target.split(".");
return keys.reduce((prev, curr) => {
if (curr.search(/\[/g) > -1) {
//if element/key in target array is array, get the value and return
let m_curr = curr.replace(/\]/g, "");
let arr = m_curr.split("[");
return arr.reduce((pr, cu) => {
return pr && pr[cu];
}, prev);
} else {
//else it is a object, get the value and return
return prev && prev[curr];
}
}, obj);
});
};
let template = "hello ${a[0][0].b.c}";
let data = {
a: [
[{
b: {
c: "world",
f: "greetings"
}
}, 2], 3
],
d: 12,
e: 14
}
console.log(interpolate(template, { ...data
}));
You should emulate a template literal instead, because letting text from ~somewhere~ run arbitrary JavaScript like a real template literal’s ${} sections can usually isn’t a good idea:
generatedText.replace(/\$\{variable}/g, variable);
JSON.parse takes serialized JSON as an argument and deserializes it. The second (optional) argument is a reviver function, taking a key and value and returning the replacement value in the deserialized object. It is documented behavior that the reviver, if it returns undefined or nothing, the property in question will be omitted from the resulting deserialized object.
I have a situation where I would like the property in question to be INCLUDED in the resulting deserialized object with the value of undefined.
So, for example, the following is currently true:
JSON.parse(JSON.stringify({a: 1, b:2}), (k, v) => v===2 ? undefined : v);
// result of this is {a:1}, as is documented/expected.
But what if I actually want the result to be
{a:1, b:undefined}
Is there any way to write the reviver to do this?
I specifically don't want to iterate through the object again after the deserialization, so please don't suggest that as a solution. Also I specifically don't want b to be set as null. I really want it to be present as a property with the value undefined.
It may simply not be possible, but I'm hoping someone has a nifty idea!
FIRST EDIT:
First suggestion was great try, but I also need it to work in deep structures, so the following:
JSON.parse(JSON.stringify({
a: 1,
b:{
c: 1,
d: 2,
e: [
1,
2,
{
f: 1,
g: 2
},
4
]
}
}), (k, v) => v===2 ? undefined : v);
should in my ideal world yield:
{
a: 1,
b:{
c: 1,
d: undefined,
e: [
1,
undefined,
{
f: 1,
g: undefined
},
4
]
}
}
I doubt this can be done with a simple reviver and JSON.parse.
I don't know if this will satisfy your constraints, and it's ugly as sin, but here's an approach: wrap JSON.parse in something that stores the undefined keys from your reviver and adds them back at the end. This does not entail iterating your object again, which you explicitly rejected, but it does iterate the list of undefined keys:
const myParse = (obj, reviver) => {
if (typeof reviver !== 'function') {
return JSON.parse(obj)
}
const undefs = []
const rev = (k, v) => {
const val = reviver(k, v)
if (typeof val === 'undefined') {
undefs.push(k)
}
return val;
}
const ret = JSON.parse(obj, rev)
undefs.forEach(k => ret[k] = undefined)
return ret
}
const result = myParse(
JSON.stringify({a: 1, b:2}),
(k, v) => v===2 ? undefined : v
)
console.log(result)
I am trying to deep-clone an object, say "a" with k = JSON.parse(JSON.stringify(a)). It is important that I use the stringify way, since I am trying to save the object into a file and then load from it.
I stumbled upon a problem with references on the cloned object which is illustrated below:
var obj={};
obj.importantProperty={s:2};
obj.c=obj.importantProperty;
obj.d=obj.importantProperty;
console.log( obj.c === obj.d ); // Returns true
var cloned = JSON.parse(JSON.stringify(obj));
console.log( cloned.c === cloned.d ); // Returns false
I need the references to be kept when using JSON.parse, in the above example they are not. In my project the object is much more complicated, but in the end it comes down to the example above.
Thanks in advance to anyone who helps me with this :)
The proper way to do something like this would be to store the common referenced object(s) separately and reference it by an ID.
For instance, you can hold your importantProperty objects in an array and use the index as the ID:
var importantProperties = [
{ s: 1 },
{ s: 2 },
{ s: 3 }
];
var obj = {};
obj.importantProperty = importantProperties[1];
obj.c = obj.importantProperty;
obj.d = obj.importantProperty;
Then when you stringify the object you replace the referenced object with its index:
var stringified = JSON.stringify(obj, function(key, value) {
if (key) {
return importantProperties.indexOf(value);
}
return value;
});
console.log(stringified);
// prints {"importantProperty":1,"c":1,"d":1}
And then when you parse you simply reverse the process to revive the references:
var parsed = JSON.parse(stringified, function(key, value) {
if (key) {
return importantProperties[value];
}
return value;
});
console.log(parsed.c === parsed.d && parsed.d === parsed.importantProperty);
// prints true
Now, the example above works for your example code under the assumption that all properties in obj is an object from the importantProperties array. If that's not the case and it's only certain properties that is an importantProperties object, you need to check for that when replacing/reviving.
Assuming only the "importantProperty", "c" and "d" properties are such objects:
if (['importantProperty', 'c', 'd'].includes(key)) instead of just if (key)
If this isn't good enough and you don't want the property name to have anything to do with whether or not the value is an importantProperties object, you'll need to indicate this in the value together with the identifier. Here's an example of how this can be done:
// Replacing
JSON.stringify(obj, function(k, value) {
if (importantProperties.includes(value)) {
return 'ImportantProperty['
+ importantProperties.indexOf(value)
+ ']';
}
return value;
});
// Reviving
JSON.parse(stringified, function(k, value) {
if (/^ImportantProperty\[\d+\]$/.test(value)) {
var index = Number( value.match(/\d+/)[0] );
return importantProperties[index];
}
return value;
});
It is impossible to achieve your desired result using JSON because JSON format can contain only a limited ammount of data types (http://json.org/) and when you stringify an object to JSON some information gets lost.
Probably there is some other kind of serialization technique, but I would recommend you to look for another approach to store data.
Python's get method for dictionaries lets me specify what should be returned if a key doesn't exist. For my current case I want a dictionary returned. How do I do this in Javascript?
There is no javascript equivalent of the python dictionary get method. If you would write it yourself, as a function, it would look like this:
function get(object, key, default_value) {
var result = object[key];
return (typeof result !== "undefined") ? result : default_value;
}
Use it like:
var obj = {"a": 1};
get(obj, "a", 2); // -> 1
get(obj, "b", 2); // -> 2
Note that the requested key will also be found in a prototype of obj.
If you really want a method rather than a function (obj.get("a", 2)), you need to extend the prototype of Object. This is generally considered a bad idea though, see Extending Object.prototype JavaScript
With modern javascript you can use the nullish coalescing operator ??
const result = obj[key] ?? default;
This will return the default value if key doesn't exist in obj. It will also return the default in cases like {myKey: undefined} or {myKey: null}, which may or may not be the desired behavior.
JavaScript has no helper feature to do that. You need to test explicitly.
if ("myProperty" in myObject) {
return { another: "object" };
} else {
return myObject.myProperty;
}
You can use a ternary operator to do the same thing with less code.
return ("myProperty" in myObject) ? myObject.myProperty : { another: "object" };
I prefer to use the logical OR like this:
foo.bar || 'default'
If checks is foo.bar is falsy, so it returns 'default' if bar is undefined.
You just need to care, that foo is an object. Otherwise a ReferenceError is thrown.
You could use a proxy for this (really new ):
var handler = {
get: function(target, name){
return name in target?
target[name] :
"Default";
}
};
var dictionary={"hi":true};
var dict = new Proxy(dictionary, handler);
dict.a = 1;
dict.b = undefined;
console.log(dict.a, dict.b,dict.hi); // 1, undefined,true
console.log(dict.new); //"Default"
//the proxied object gets changed:
console.log(dictionary.a, dictionary.b,dictionary.hi); // 1, undefined,true
console.log(dictionary.new); //undefined
A proxy is an object that reflects all changes and requests trough an handler. In this case we can write/access propertys of dictionary normally, but if we access values that do not exist it'll return "Default"
this works for me
let obj = {"a": 1};
let default = 100
obj["a"] || default; // -> 1
obj["b"] || default; // -> 100
But! there are some limitation, if !!obj["a"] === false we always get default value... so it's better to just check if key in obj, to be completely sure.