Substringing JSON keys - javascript

I have a form with inputs using this naming convetion:
<input class="xxlarge" name="note[url]" id="url" placeholder="URL">
So, I'm using this script (found on StackOverflow) that serializes form data into JSON.
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
and on the output I have this:
{"note[url]":"URL","note[title]":"TITLE"}
I'd like to know how to transform this script to get output like this:
{"url":"URL","title":"TITLE"}
I'm handling this from with rather standard, documented code block (using function, described above):
$(function() {
$('form').submit(function() {
$('#result').html(JSON.stringify($('form').serializeObject()));
$.post(
"/api/create",
JSON.stringify($('form').serializeObject()),
function(responseText){
$("#result").html(responseText);
},
"html"
);
return false;
});
Thanks in advance!

I would suggest parsing the string into a JS object, changing the keys in a for loop, then stringifying it when you're done. Like so:
// turn the string into a JS object
var data = JSON.parse('{"note[url]":"URL","note[title]":"TITLE"}');
var newData = {};
// step through each member
for(key in data) {
// Regular expressions to find the brackets
var newKeyStart = key.search(/note\[/) + 5;
var newKeyEnd = key.search(/\]/);
// pull out the desired part of the key
var newKey = key.substr(newKeyStart, newKeyEnd - newKeyStart);
// insert into new data object
newData[newKey] = data[key];
}
// turn back into JSON again
var newJSON = JSON.stringify(newData);

Not sure where your 'note' part is coming from. May be something you could fix via the name attributes in your markup. Otherwise you could always do something like:
function renameKeys(obj) {
var
result = {},
key,
check,
noteReg = /^note\[([^\]]+)\]$/;
for(key in obj) {
result[(check = key.match(noteReg)) === null ? key : check[1]] = typeof obj[key] == 'object' && toString.call(obj[key]) == '[object Object]' ? renameKeys(obj[key]) : obj[key];
}
return result;
}
which can be used to make a new object with the keys you want.
renameKeys({"note[url]":"URL","note[title]":"TITLE"});
// { url: 'URL', title: 'TITLE' }
renameKeys({"note[url]":"URL","note[title]":"TITLE", anotherObj: { thingA: 1234, 'note[thingB]': 9492}});
// { url: 'URL', title: 'TITLE', anotherObj: { thingA: 1234, thingB: 9492 } }
Beware, though, that if you have something like a key of note[asdf] and a key of asdf then whichever is iterated over last will overwrite the other.

Related

Could you have multiple ways to run a function in Javascript?

I am setting up a database package in Node.js and would like to not have separate functions for writing to the database like this:
write(key, val) and write({key: val, key2: val2}). I've seen other solutions on Stack Overflow and other websites and would like to have the simplest solution so my function would "know" whether it was a key, val pair or a JSON object. For example:
if (argtype == "kvp") { // key val pair
databaseJSON[key] = val;
flushToDB(databaseJSON);
} else {
let j = databaseJSON;
for (let i in Object.values(obj)) j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
}
Thank you!
If two arguments are passed, just assign the val to the key property of the database, otherwise Object.assign the one argument to the database, to put all of its properties and values from the passed object to the database:
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
const database = {};
const flushToDB = db => console.log('flushing');
function write(key, val) {
if (val !== undefined) {
database[key] = val;
} else {
Object.assign(database, key);
}
flushToDB(database)
}
write('key', 'val');
console.log('db:', database);
write({ key2: 'val2', key3: 'val3' });
console.log('db:', database);
Because the database here is a plain object, not JSON (something in JSON format is a string, which is not the case here), better to call it database rather than databaseJSON. (See There's no such thing as a "JSON Object")
You can use this.
function foo() {
if (arguments.length == 1) {
// use your object code on arguments[0]
let j = databaseJSON;
let obj = arguments[0]
for (let i in Object.values(obj))
j[Object.keys(obj)[i]] = Object.values(obj)[i];
flushToDB(databaseJSON);
} else {
//user arguments[0] and arguments[1] for you key value
databaseJSON[arguments[0]] = arguments[1];
flushToDB(databaseJSON);
}
}
You can also throw error if arguments.length == 0

JavaScript find property in JSON object

I have a JSON object that looks a bit like this:
{
name: 'test',
details: {
description: 'This is the long description',
shortDescription: 'This is the short description (ironically longer than the description!)'
}
}
Obviously the real object is a lot more complicated than this example, but I have omitted the details because they will only complicate the question.
So, with this object, I have a function that tries to get the value of the property, it looks like this:
// Private function for matching fields
var _matchField = function (item, filter) {
// Our variables
var text = item[filter.field],
values = filter.expression.split(',');
// If we have any text
if (text) {
// Loop through our values
angular.forEach(values, function (value) {
console.log(text);
console.log(value);
// See if we have a match
if (text.toLowerCase().indexOf(value.toLowerCase()) > -1) {
// We have found a match
return true;
}
});
}
// We have found no matches
return false;
}
The issue is the line:
var text = item[filter.field],
If the property was just the name then item['name'] would work with the above object. But if I want to get the description; item['details.descrption'] doesn't work.
So I need a function that will allow me to specify a property name and it will find the property and return its value.
But before I try to write one, I was hoping there might be a simple solution that someone has come across.
you can write your custom function for this
function getProperty(json, field) {
if (json == null || field == null) {
return null;
}
var value = json;
var fields = field.split(".");
for (var i = 0; i < fields.length; i++) {
value = value[fields[i]];
if (value == null) {
return null;
}
}
return value;
}
check this plnkr example https://plnkr.co/edit/8Ayd9wnh1rJh1ycx5R1f?p=preview
You can split the reference to the object and use a function for getting the right nested object/value.
function getValue(o, p) {
if (typeof p === 'string') {
p = p.split('.')
}
return p.length ? getValue(o[p.shift()], p) : o;
}
var item = { name: 'test', details: { description: 'This is the long description', shortDescription: 'This is the short description (ironically longer than the description!)' } };
document.write(getValue(item, 'details.description'));
I solved this by creating this function:
// Private function to get the value of the property
var _getPropertyValue = function (object, notation) {
// Get all the properties
var properties = notation.split('.');
// If we only have one property
if (properties.length === 1) {
// Return our value
return object[properties];
}
// Loop through our properties
for (var property in object) {
// Make sure we are a property
if (object.hasOwnProperty(property)) {
// If we our property name is the same as our first property
if (property === properties[0]) {
// Remove the first item from our properties
properties.splice(0, 1);
// Create our new dot notation
var dotNotation = properties.join('.');
// Find the value of the new dot notation
return _getPropertyValue(object[property], dotNotation);
}
}
}
};

Undefined counting as a variable -- messes up with my isObjEmpty() function

I am trying to post an object only if it's not empty. However I have code which causes properties to become undefined -- and when that happens, the obj is not empty anymore and the post still happens.
userSearchData = {};
$('#addContactForm').keyup(function()
{
var email = $(this).find('input[name="email"]').val();
var username = $(this).find('input[name="username"]').val();
var fullName = $(this).find('input[name="fullName"]').val();
userSearchData.email = email.length >= 3 ? email : undefined;
userSearchData.username = username.length >= 3 ? username : undefined;
userSearchData.fullName = fullName.length >= 3 ? fullName : undefined;
console.log(userSearchData);
if ( !isEmpty(userSearchData) )
{
console.log("Is empty")
$.post( '/post/addContact', { userSearchData: userSearchData }, function( data )
{
console.log( data );
});
}
});
It's a "search" form, so if a user types for example "Blabla" as the username, and then erases letters to make it "Bl", then the username variable gets undefined, so it's not being sent when doing the post (I console log the object on the server side and the undefined variables are not considered which is good).
How can I make my variables completely removed, instead of undefined when their length is below 3?
I could probably modify the isEmpty function to return false if all keys are undefined, would that be better to do that? If so, how would you do it?
var hasOwnProperty = Object.prototype.hasOwnProperty;
function isEmpty (obj)
{
// null and undefined are "empty"
if (obj == null) return true;
// Assume if it has a length property with a non-zero value
// that that property is correct.
if (obj.length > 0) return false;
if (obj.length === 0) return true;
// Otherwise, does it have any properties of its own?
// Note that this doesn't handle
// toString and valueOf enumeration bugs in IE < 9
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) return false;
}
return true;
}
The whole thing seems rather pointless, you can just do this instead
$('#addContactForm').on('keyup', function() {
var userSearchData = {}, self = this;
$.each(['email', 'username', 'fullName'], function(_, el) {
var val = $(self).find('input[name="'+el+'"]').val();
if ( val.length > 3 ) userSearchData[el] = val;
});
$.post( '/post/addContact', { userSearchData: userSearchData }, function( data ) {
console.log( data );
});
});
Only add the properties to the object if the condition is met.
if ( username.length >=3 ) {
userSearchData.username = username;
}
if ( username in userSearchData ) {
// do stuff
}
you can delete properties in JS, but the better fix is to just make sure your code posts when it should.
if (obj === null || obj === undefined) return;
or something might help you here.
Also, for(key in obj) is old-style "iterate over prototype as well", and highly discouraged, so you probably want this instead:
var keys = Object.keys(obj);
if(keys.length === 0) ...
keys.forEach(function(key) { ... });
Do you mean you want do this?
if(!isEmpty(userSearchData)){
$.post( '/post/addContact', { userSearchData: userSearchData }, function( data )
{
console.log( data );
});
}

Push element into JSON object

I want to go through a JSON, if a certain condition applies then push some extra elements in that index.
I have this JS code:
$scope.addRoleToUser = function() {
var userid = $scope.selectedUser;
var tmpdata = [];
var index = 0;
//alert(userid);
angular.forEach($scope.users, function(data) {
if (data.id == $scope.selectedUser) {
tmpdata.push(data,{"roles":[{"id":"00","name":"newrole"}]});
}
else {
tmpdata.push(data);
}
index++;
});
$scope.users = tmpdata;
};
This is my initial JSON element:
$scope.users = [
{"id":"0","name":"User1","roles":[{}]},
{"id":"1","name":"User2","roles":[{}]},
]
I'm trying to get it to look like this after the function runs:
$scope.users = [
{"id":"0","name":"User1","roles":[{"id":"00","name":"newrole"}]},
{"id":"1","name":"User2","roles":[{}]},
]
But instead I'm getting this:
[{"id":"0","name":"User1","roles":[{}]},{"roles":[{"id":"00","name":"newrole"}]},{"id":"1","name":"User2","roles":[{}]}]
Just replace this inside your function
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}];
}
Or, if you know that roles is not empty, you can do:
if (data.id == $scope.selectedUser) {
data.roles.push({"id":"00","name":"newrole"});
}
And after this line you can add your data to tmpdata!
That snippet now will look like this:
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}]}); //or the other one
}
tmpdata.push(data);
Inside the forEach() callback you're just working with objects and as such, you can modify them directly inside the callback:
angular.forEach($scope.users, function(data) {
if (data.id == $scope.selectedUser) {
data.roles = [{"id":"00","name":"newrole"}];
}
});
Similarly you could modify almost anything of each entry by manipulating the respective data object.
Example Fiddle
The Array.prototype.push method is variadic: (https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push).
When you call tmpdata.push(a,b,c), you are in essence appending the array [a,b,c] to tmpdata.
You can also decompose the problem with something like:
$scope.addRoleToUser = function() {
var thisUserid = $scope.selectedUser;
function addRolesFor(user) {
if (user.id === thisUserId){ user.roles = [{"id":"00","name":"newrole"}] };
return user;
}
retrun $scope.users.map(addRoles);
}
Please use the map function that is appropriate for your environment (like _.map), because the Array.prototype.map method is not supported by all browsers.

Ember model to json

I am looking for an efficient way to translate my Ember object to a json string, to use it in a websocket message below
/*
* Model
*/
App.node = Ember.Object.extend({
name: 'theName',
type: 'theType',
value: 'theValue',
})
The websocket method:
App.io.emit('node', {node: hash});
hash should be the json representation of the node. {name: thename, type: theType, ..}
There must be a fast onliner to do this.. I dont want to do it manualy since i have many attributes and they are likely to change..
As stated you can take inspiration from the ember-runtime/lib/core.js#inspect function to get the keys of an object, see http://jsfiddle.net/pangratz666/UUusD/
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, ret = [];
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
} // ignore useless items
if (Ember.typeOf(v) === 'function') {
continue;
}
ret.push(key);
}
}
return this.getProperties.apply(this, ret);
}
});
Note, since commit 1124005 - which is available in ember-latest.js and in the next release - you can pass the ret array directly to getProperties, so the return statement of the getJson function looks like this:
return this.getProperties(ret);
You can get a plain JS object (or hash) from an Ember.Object instance by calling getProperties() with a list of keys.
If you want it as a string, you can use JSON.stringify().
For example:
var obj = Ember.Object.create({firstName: 'Erik', lastName: 'Bryn', login: 'ebryn'}),
hash = obj.getProperties('firstName', 'lastName'), // => {firstName: 'Erik', lastName: 'Bryn'}
stringHash = JSON.stringify(hash); // => '{"firstName": "Erik", "lastName": "Bryn"}'
I have also been struggling with this. As Mirko says, if you pass the ember object to JSON.stringify you will get circular reference error. However if you store the object inside one property and use stringify on that object, it works, even nested subproperties.
var node = Ember.Object.create({
data: {
name: 'theName',
type: 'theType',
value: 'theValue'
}
});
console.log(JSON.stringify(node.get('data')));
However, this only works in Chrome, Safari and Firefox. In IE8 I get a stack overflow so this isn't a viable solution.
I have resorted to creating JSON schemas over my object models and written a recursive function to iterate over the objects using the properties in the schemas and then construct pure Javascript objects which I can then stringify and send to my server. I also use the schemas for validation so this solution works pretty well for me but if you have very large and dynamic data models this isn't possible. I'm also interested in simpler ways to accomplish this.
I modifed #pangratz solution slightly to make it handle nested hierarchies of Jsonables:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
App.io.emit('node', {node: node.toJSON()});
Or if you have an ID property and want to include it:
App.io.emit('node', {node: node.toJSON({includeId: true})});
Will this work for you?
var json = JSON.stringify( Ember.getMeta( App.node, 'values') );
The false is optional, but would be more performant if you do not intend to modify any of the properties, which is the case according to your question. This works for me, but I am wary that Ember.meta is a private method and may work differently or not even be available in future releases. (Although, it isn't immediately clear to me if Ember.getMeta() is private). You can view it in its latest source form here:
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/utils.js
The values property contains only 'normal' properties. You can collect any cached, computed properties from Ember.meta( App.node, false ).cached. So, provided you use jQuery with your build, you can easily merge these two objects like so:
$.extend( {}, Ember.getMeta(App.node, 'values'), Ember.getMeta(App.node, 'cache') );
Sadly, I haven't found a way to get sub-structures like array properties in this manner.
I've written an extensive article on how you can convert ember models into native objects or JSON which may help you or others :)
http://pixelchild.com.au/post/44614363941/how-to-convert-ember-objects-to-json
http://byronsalau.com/blog/convert-ember-objects-to-json/
I modified #Kevin-pauli solution to make it works with arrays as well:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I also made some further modification to get the best of both worlds. With the following version I check if the Jsonable object has a specific property that informs me on which of its properties should be serialized:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, base, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
if (!Ember.isNone(this.get('jsonProperties'))) {
// the object has a selective list of properties to inspect
base = this.getProperties(this.get('jsonProperties'));
} else {
// no list given: let's use all the properties
base = this;
}
for (var key in base) {
if (base.hasOwnProperty(key)) {
v = base[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I am using this little tweak and I am happy with it. I hope it'll help others as well!
Thanks to #pangratz and #Kevin-Pauli for their solution!
Here I take #leo, #pangratz and #kevin-pauli solution a little step further. Now it iterates not only with arrays but also through has many relationships, it doesn't check if a value has the type Array but it calls the isArray function defined in Ember's API.
Coffeescript
App.Jsonable = Em.Mixin.create
getJson: ->
jsonValue = (attr) ->
return attr.map(jsonValue) if Em.isArray(attr)
return attr.getJson() if App.Jsonable.detect(attr)
attr
base =
if Em.isNone(#get('jsonProperties'))
# no list given: let's use all the properties
this
else
# the object has a selective list of properties to inspect
#getProperties(#get('jsonProperties'))
hash = {}
for own key, value of base
continue if value is 'toString' or Em.typeOf(value) is 'function'
json[key] = jsonValue(value)
json
Javascript
var hasProp = {}.hasOwnProperty;
App.Jsonable = Em.Mixin.create({
getJson: function() {
var base, hash, hashValue, key, value;
jsonValue = function(attr) {
if (Em.isArray(attr)) {
return attr.map(jsonValue);
}
if (App.Jsonable.detect(attr)) {
return attr.getJson();
}
return attr;
};
base = Em.isNone(this.get('jsonProperties')) ? this : this.getProperties(this.get('jsonProperties'));
json = {};
for (key in base) {
if (!hasProp.call(base, key)) continue;
value = base[key];
if (value === 'toString' || Em.typeOf(value) === 'function') {
continue;
}
json[key] = jsonValue(value);
}
return json;
}
});
Ember Data Model's object counts with a toJSON method which optionally receives an plain object with includeId property used to convert an Ember Data Model into a JSON with the properties of the model.
https://api.emberjs.com/ember-data/2.10/classes/DS.Model/methods/toJSON?anchor=toJSON
You can use it as follows:
const objects = models.map((model) => model.toJSON({ includeId: true }));
Hope it helps. Enjoy!
I have:
fixed and simplified code
added circular reference prevention
added use of get of value
removed all of the default properties of an empty component
//Modified by Shimon Doodkin
//Based on answers of: #leo, #pangratz, #kevin-pauli, #Klaus
//http://stackoverflow.com/questions/8669340
App.Jsonable = Em.Mixin.create({
getJson : function (keysToSkip, visited) {
//getJson() called with no arguments,
// they are to pass on values during recursion.
if (!keysToSkip)
keysToSkip = Object.keys(Ember.Component.create());
if (!visited)
visited = [];
visited.push(this);
var getIsFunction;
var jsonValue = function (attr, key, obj) {
if (Em.isArray(attr))
return attr.map(jsonValue);
if (App.Jsonable.detect(attr))
return attr.getJson(keysToSkip, visited);
return getIsFunction?obj.get(key):attr;
};
var base;
if (!Em.isNone(this.get('jsonProperties')))
base = this.getProperties(this.get('jsonProperties'));
else
base = this;
getIsFunction=Em.typeOf(base.get) === 'function';
var json = {};
var hasProp = Object.prototype.hasOwnProperty;
for (var key in base) {
if (!hasProp.call(base, key) || keysToSkip.indexOf(key) != -1)
continue;
var value = base[key];
// there are usual circular references
// on keys: ownerView, controller, context === base
if ( value === base ||
value === 'toString' ||
Em.typeOf(value) === 'function')
continue;
// optional, works also without this,
// the rule above if value === base covers the usual case
if (visited.indexOf(value) != -1)
continue;
json[key] = jsonValue(value, key, base);
}
visited.pop();
return json;
}
});
/*
example:
DeliveryInfoInput = Ember.Object.extend(App.Jsonable,{
jsonProperties: ["title","value","name"], //Optionally specify properties for json
title:"",
value:"",
input:false,
textarea:false,
size:22,
rows:"",
name:"",
hint:""
})
*/
Ember.js appears to have a JSON library available. I hopped into a console (Firebug) on one the Todos example and the following worked for me:
hash = { test:4 }
JSON.stringify(hash)
So you should be able to just change your line to
App.io.emit('node', { node:JSON.stringify(hash) })

Categories