Related
I've been trying for a couple hours to do this, most questions and examples I've seen are not addressing my problem, for instance This one here is talking about the keys, not values.
I tired using the JSON parser, but there are two issues:
How do I iterate through the values to begin with? I know there are different ways to read keys, but what about values and nested values (given that we don't know anything about what the keys or values are called).
How to actually write the value and replace it, rather than replacing the whole file, maybe something like:
key.value=key.value.toUpper();
I am looking for a solution that works for any JSON file, with absolultly no knowledge what the keys are called.
You could use replace to operate on the JSON string directly:
jsonString.replace(/"\s*:\s*"[^"]/g, match => {
return match.slice(0, -1) + match[match.length - 1].toUpperCase()
})
That would save you from having to parse the JSON, and might be a bit faster. It can be tough to write a performant comprehensive RegEx though, so it might be safer just to parse the JSON and write a little recursive function:
const uppercaseValues = obj => {
return Object.keys(obj).reduce((uppercased, key) => {
const value = obj[key]
if (typeof value === 'string') {
uppercased[key] = value[0].toUpperCase() + value.slice(1)
} else if (typeof value === 'object') {
uppercased[key] = uppercaseValues(value)
} else {
uppercased[key] = value
}
return uppercased
}, {})
}
const parsedJson = JSON.parse(jsonString)
const uppercased = uppercaseValues(parsedJson)
const xformedJson = JSON.stringify(uppercased)
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.
I have an object (parse tree) that contains child nodes which are references to other nodes.
I'd like to serialize this object, using JSON.stringify(), but I get
TypeError: cyclic object value
because of the constructs I mentioned.
How could I work around this? It does not matter to me whether these references to other nodes are represented or not in the serialized object.
On the other hand, removing these properties from the object when they are being created seems tedious and I wouldn't want to make changes to the parser (narcissus).
Use the second parameter of stringify, the replacer function, to exclude already serialized objects:
var seen = [];
JSON.stringify(obj, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
});
http://jsfiddle.net/mH6cJ/38/
As correctly pointed out in other comments, this code removes every "seen" object, not only "recursive" ones.
For example, for:
a = {x:1};
obj = [a, a];
the result will be incorrect. If your structure is like this, you might want to use Crockford's decycle or this (simpler) function which just replaces recursive references with nulls:
function decycle(obj, stack = []) {
if (!obj || typeof obj !== 'object')
return obj;
if (stack.includes(obj))
return null;
let s = stack.concat([obj]);
return Array.isArray(obj)
? obj.map(x => decycle(x, s))
: Object.fromEntries(
Object.entries(obj)
.map(([k, v]) => [k, decycle(v, s)]));
}
//
let a = {b: [1, 2, 3]}
a.b.push(a);
console.log(JSON.stringify(decycle(a)))
This is kind of an alternate-answer, but since what a lot of people will come here for is debugging their circular objects and there's not really a great way to do that without pulling in a bunch of code, here goes.
One feature that's not as well-known as JSON.stringify() is console.table(). Simply call console.table(whatever);, and it will log the variable in the console in tabular format, making it rather quite easy and convenient to peruse the variable's contents.
Here is an example of a data structure with cyclic references:
function makeToolshed(){
var nut = {name: 'nut'}, bolt = {name: 'bolt'};
nut.needs = bolt; bolt.needs = nut;
return { nut: nut, bolt: bolt };
}
When you wish to KEEP the cyclic references (restore them when you deserialize, instead of "nuking" them), you have 2 choices, which I'll compare here. First is Douglas Crockford's cycle.js, second is my siberia package. Both work by first "decycling" the object, i.e., constructing another object (without any cyclic references) "containing the same information."
Mr. Crockford goes first:
JSON.decycle(makeToolshed())
As you see, the nested structure of JSON is retained, but there is a new thing, which is objects with the special $ref property. Let's see how that works.
root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
The dollar sign stands for the root. .bolt having $ref tells us that .bolt is an "already seen" object, and the value of that special property (here, the string $["nut"]["needs"]) tells us where, see first === above. Likewise for second $ref and the second === above.
Let's use a suitable deep equality test (namely Anders Kaseorg's deepGraphEqual function from accepted answer to this question) to see if cloning works.
root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true
Now, siberia:
JSON.Siberia.forestify(makeToolshed())
Siberia does not try to mimic "classic" JSON, no nested structure. The object graph is described in a "flat" manner.
Each node of the object graph is turned into a flat tree (plain key value pair list with integer-only values), which is an entry in .forest. At index zero, we find the root object, at higher indices, we find the other nodes of the object graph, and negative values (of some key of some tree of the forest) point to the atoms array, (which is typed via the types array, but we'll skip the typing details here). All terminal nodes are in the atoms table, all non-terminal nodes are in the forest table, and you can see right away how many nodes the object graph has, namely forest.length. Let's test if it works:
root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true
comparison
will add section later.
note
I'm currently refactoring the package. Central ideas and algorithms are staying the same, but the new version will be easier to use, the top level API will be different. I will very soon archive siberia and present the refactored version, which I'll call objectgraph. Stay tuned, it will happen this month (August 2020)
ah, and ultra short version for the comparison. For a "pointer", I need as much space as an integer takes, since my "pointers to already seen nodes" (as a matter of fact, to all nodes, already seen or not) are just integers. In Mr. Crockford's version, amount needed to store a "pointer" is bounded only by the size of the object graph. That makes the worst case complexity of Mr. Crockford's version extremely horrible. Mr. Crockford gave us "another Bubblesort". I'm not kidding you. It's that bad. If you don't believe it, there are tests, you can find them starting from the readme of the package (will transform them to be benchmark.js compliant also this month, Aug 2020)
much saver and it shows where an cycle object was.
<script>
var jsonify=function(o){
var seen=[];
var jso=JSON.stringify(o, function(k,v){
if (typeof v =='object') {
if ( !seen.indexOf(v) ) { return '__cycle__'; }
seen.push(v);
} return v;
});
return jso;
};
var obj={
g:{
d:[2,5],
j:2
},
e:10
};
obj.someloopshere = [
obj.g,
obj,
{ a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>
produces
jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}
I've created an GitHub Gist which is able to detect cyclic structures and also de- and encodes them: https://gist.github.com/Hoff97/9842228
To transform just use JSONE.stringify/JSONE.parse.
It also de- and encodes functions. If you want to disable this just remove lines 32-48 and 61-85.
var strg = JSONE.stringify(cyclicObject);
var cycObject = JSONE.parse(strg);
You can find an example fiddle here:
http://jsfiddle.net/hoff97/7UYd4/
I create too a github project that can serialize cyclic object and restore the class if you save it in the serializename attribute like a String
var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal( b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal( retCaseDep.b, 25 );
assert.equal( retCaseDep.enfant.papa, retCaseDep );
https://github.com/bormat/serializeStringifyParseCyclicObject
Edit:
I have transform my script for NPM https://github.com/bormat/borto_circular_serialize and I have change function names from french to english.
the nodejs module serialijse provides a nice way to deal with any type of JSON objects containing cycles or javascript class instances.
const { serialize, deserialize } = require("serialijse");
var Mary = { name: "Mary", friends: [] };
var Bob = { name: "Bob", friends: [] };
Mary.friends.push(Bob);
Bob.friends.push(Mary);
var group = [ Mary, Bob];
console.log(group);
// testing serialization using JSON.stringify/JSON.parse
try {
var jstr = JSON.stringify(group);
var jo = JSON.parse(jstr);
console.log(jo);
} catch (err) {
console.log(" JSON has failed to manage object with cyclic deps");
console.log(" and has generated the following error message", err.message);
}
// now testing serialization using serialijse serialize/deserialize
var str = serialize(group);
var so = deserialize(str);
console.log(" However Serialijse knows to manage object with cyclic deps !");
console.log(so);
assert(so[0].friends[0] == so[1]); // Mary's friend is Bob
this serializer supports
cycle in the object definition
reconstruction of class's instance
support for Typed Array, Map, and Set
ability to filter properties to skip during the serialization process.
binary encoding of Typed Array (Float32Array etc ... ) for performance.
function stringifyObject ( obj ) {
if ( _.isArray( obj ) || !_.isObject( obj ) ) {
return obj.toString()
}
var seen = [];
return JSON.stringify(
obj,
function( key, val ) {
if (val != null && typeof val == "object") {
if ( seen.indexOf( val ) >= 0 )
return
seen.push( val )
}
return val
}
);
}
A precondition was missing, otherwise the integer values in array objects are truncated, i.e. [[ 08.11.2014 12:30:13, 1095 ]] 1095 gets reduced to 095.
I am doing a project which requires to pass Perl objects to javascript via JSON. I am facing a problem in terms of "intermediate" object definition.
In Perl, object is represented by hash, and programmers don't have to define anything "in the middle". Once a property is created, all intermediate objects are automatically created as hash references. e.g.
$graph{chart}{yAxis}{title} = "Temperature Tracking";
However, once this object is passed to Javascript, if I want to add any new properties in the "intermediate" object, like:
graph.chart.xAxis.title = "Time Sequence";
I'll have an "undefined graph.chart.xAxis" error. Unlike Perl, Javascript doesn't automatically create objects if we simply assign a property for it.
At the moment I have to use below solution:
if (!graph.chart.xAxis) {
graph.chart.xAxis = {};
graph.chart.xAxis.title = "Time Sequence";
}
Unfortunately, in our project the objects passed from Perl are pretty dynamic and there are plenty of other objects that Javascript may not know. Above way makes JS code pretty lengthy and "ugly looking". Are there any better solutions to make Javascript behave like Perl, which means I don't have to create intermediate objects manually?
I'm not sure whether this meets your requirements but a simple function to create the missing objects could look like this:
function insertNode(obj, node, value) {
var segments = node.split('.');
var key = segments.pop();
var ref = obj;
while (segments.length > 0) {
var segment = segments.shift();
if (typeof ref[segment] != 'object') {
ref[segment] = {};
}
ref = ref[segment];
}
ref[key] = value;
};
var x = {};
insertNode(x, 'foo.bar.baz', 'hello world');
alert(x.foo.bar.baz); // "hello world"
Demo: http://jsfiddle.net/sNywt/1/
You may be interested in a library called steeltoe
steelToe(graph).set('chart.xAxis.title', 'Time Sequence');
not sure, if its suitable in your case, but you could check for property existence and create it if does not exist, like:
function use_or_create(obj, prop) {
return (obj.hasOwnProperty(prop)) ? true : (obj[prop] = {});
}
var graph.chart = {}; //your object
//function call to check if property exist before trying to use it
use_or_create(graph.chart, 'xAxis'); //check if xAxis exists and creates one if doesnot
graph.char.xAxis.title = "tested";
Maybe this Object extension will do:
Object.prototype.val = function(prop,val){
prop = /\./i.test(prop) ? prop.split('.') : prop;
if (prop.constructor === Array){
var objnow = this, pr;
while (pr = prop.shift()){
if (!objnow[pr]){
objnow[pr] = {};
}
if (!prop.length) {
objnow[pr] = val;
}
objnow = objnow[pr];
}
for (var l in objnow){
this[l] = objnow[l];
}
} else {
this[prop] = val;
}
}
// usage
var myO = {};
myO.val('a.b.c',3); //=> myO.a.b.c = 3
myO.val('someprop',3); //=> myO.someprop = 3
myO.val('a.b.someprop',5); //=> myO.a.b.someprop = 3
I have an object (parse tree) that contains child nodes which are references to other nodes.
I'd like to serialize this object, using JSON.stringify(), but I get
TypeError: cyclic object value
because of the constructs I mentioned.
How could I work around this? It does not matter to me whether these references to other nodes are represented or not in the serialized object.
On the other hand, removing these properties from the object when they are being created seems tedious and I wouldn't want to make changes to the parser (narcissus).
Use the second parameter of stringify, the replacer function, to exclude already serialized objects:
var seen = [];
JSON.stringify(obj, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
});
http://jsfiddle.net/mH6cJ/38/
As correctly pointed out in other comments, this code removes every "seen" object, not only "recursive" ones.
For example, for:
a = {x:1};
obj = [a, a];
the result will be incorrect. If your structure is like this, you might want to use Crockford's decycle or this (simpler) function which just replaces recursive references with nulls:
function decycle(obj, stack = []) {
if (!obj || typeof obj !== 'object')
return obj;
if (stack.includes(obj))
return null;
let s = stack.concat([obj]);
return Array.isArray(obj)
? obj.map(x => decycle(x, s))
: Object.fromEntries(
Object.entries(obj)
.map(([k, v]) => [k, decycle(v, s)]));
}
//
let a = {b: [1, 2, 3]}
a.b.push(a);
console.log(JSON.stringify(decycle(a)))
This is kind of an alternate-answer, but since what a lot of people will come here for is debugging their circular objects and there's not really a great way to do that without pulling in a bunch of code, here goes.
One feature that's not as well-known as JSON.stringify() is console.table(). Simply call console.table(whatever);, and it will log the variable in the console in tabular format, making it rather quite easy and convenient to peruse the variable's contents.
Here is an example of a data structure with cyclic references:
function makeToolshed(){
var nut = {name: 'nut'}, bolt = {name: 'bolt'};
nut.needs = bolt; bolt.needs = nut;
return { nut: nut, bolt: bolt };
}
When you wish to KEEP the cyclic references (restore them when you deserialize, instead of "nuking" them), you have 2 choices, which I'll compare here. First is Douglas Crockford's cycle.js, second is my siberia package. Both work by first "decycling" the object, i.e., constructing another object (without any cyclic references) "containing the same information."
Mr. Crockford goes first:
JSON.decycle(makeToolshed())
As you see, the nested structure of JSON is retained, but there is a new thing, which is objects with the special $ref property. Let's see how that works.
root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
The dollar sign stands for the root. .bolt having $ref tells us that .bolt is an "already seen" object, and the value of that special property (here, the string $["nut"]["needs"]) tells us where, see first === above. Likewise for second $ref and the second === above.
Let's use a suitable deep equality test (namely Anders Kaseorg's deepGraphEqual function from accepted answer to this question) to see if cloning works.
root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true
Now, siberia:
JSON.Siberia.forestify(makeToolshed())
Siberia does not try to mimic "classic" JSON, no nested structure. The object graph is described in a "flat" manner.
Each node of the object graph is turned into a flat tree (plain key value pair list with integer-only values), which is an entry in .forest. At index zero, we find the root object, at higher indices, we find the other nodes of the object graph, and negative values (of some key of some tree of the forest) point to the atoms array, (which is typed via the types array, but we'll skip the typing details here). All terminal nodes are in the atoms table, all non-terminal nodes are in the forest table, and you can see right away how many nodes the object graph has, namely forest.length. Let's test if it works:
root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true
comparison
will add section later.
note
I'm currently refactoring the package. Central ideas and algorithms are staying the same, but the new version will be easier to use, the top level API will be different. I will very soon archive siberia and present the refactored version, which I'll call objectgraph. Stay tuned, it will happen this month (August 2020)
ah, and ultra short version for the comparison. For a "pointer", I need as much space as an integer takes, since my "pointers to already seen nodes" (as a matter of fact, to all nodes, already seen or not) are just integers. In Mr. Crockford's version, amount needed to store a "pointer" is bounded only by the size of the object graph. That makes the worst case complexity of Mr. Crockford's version extremely horrible. Mr. Crockford gave us "another Bubblesort". I'm not kidding you. It's that bad. If you don't believe it, there are tests, you can find them starting from the readme of the package (will transform them to be benchmark.js compliant also this month, Aug 2020)
much saver and it shows where an cycle object was.
<script>
var jsonify=function(o){
var seen=[];
var jso=JSON.stringify(o, function(k,v){
if (typeof v =='object') {
if ( !seen.indexOf(v) ) { return '__cycle__'; }
seen.push(v);
} return v;
});
return jso;
};
var obj={
g:{
d:[2,5],
j:2
},
e:10
};
obj.someloopshere = [
obj.g,
obj,
{ a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>
produces
jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}
I've created an GitHub Gist which is able to detect cyclic structures and also de- and encodes them: https://gist.github.com/Hoff97/9842228
To transform just use JSONE.stringify/JSONE.parse.
It also de- and encodes functions. If you want to disable this just remove lines 32-48 and 61-85.
var strg = JSONE.stringify(cyclicObject);
var cycObject = JSONE.parse(strg);
You can find an example fiddle here:
http://jsfiddle.net/hoff97/7UYd4/
I create too a github project that can serialize cyclic object and restore the class if you save it in the serializename attribute like a String
var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal( b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal( retCaseDep.b, 25 );
assert.equal( retCaseDep.enfant.papa, retCaseDep );
https://github.com/bormat/serializeStringifyParseCyclicObject
Edit:
I have transform my script for NPM https://github.com/bormat/borto_circular_serialize and I have change function names from french to english.
the nodejs module serialijse provides a nice way to deal with any type of JSON objects containing cycles or javascript class instances.
const { serialize, deserialize } = require("serialijse");
var Mary = { name: "Mary", friends: [] };
var Bob = { name: "Bob", friends: [] };
Mary.friends.push(Bob);
Bob.friends.push(Mary);
var group = [ Mary, Bob];
console.log(group);
// testing serialization using JSON.stringify/JSON.parse
try {
var jstr = JSON.stringify(group);
var jo = JSON.parse(jstr);
console.log(jo);
} catch (err) {
console.log(" JSON has failed to manage object with cyclic deps");
console.log(" and has generated the following error message", err.message);
}
// now testing serialization using serialijse serialize/deserialize
var str = serialize(group);
var so = deserialize(str);
console.log(" However Serialijse knows to manage object with cyclic deps !");
console.log(so);
assert(so[0].friends[0] == so[1]); // Mary's friend is Bob
this serializer supports
cycle in the object definition
reconstruction of class's instance
support for Typed Array, Map, and Set
ability to filter properties to skip during the serialization process.
binary encoding of Typed Array (Float32Array etc ... ) for performance.
function stringifyObject ( obj ) {
if ( _.isArray( obj ) || !_.isObject( obj ) ) {
return obj.toString()
}
var seen = [];
return JSON.stringify(
obj,
function( key, val ) {
if (val != null && typeof val == "object") {
if ( seen.indexOf( val ) >= 0 )
return
seen.push( val )
}
return val
}
);
}
A precondition was missing, otherwise the integer values in array objects are truncated, i.e. [[ 08.11.2014 12:30:13, 1095 ]] 1095 gets reduced to 095.