Related
The data in SQL contains a lengthy JSON string in a cell similar to this:
{
"Name": "Example",
"Results": [
{
"ResultId": 0,
"AnswerIds": "[1,2,33,4,5]"
},
{
"ResultId": 1,
"AnswerIds": "[2,3,4,55,6]"
}
]
}
I have a list of replacement AnswerIds: Replace all 2 with 7, all 3's with 8's
How can I go about making a script for this?
I'm able to isolate the AnswerIds using crossapply and JSON_Query, but not sure how to go about replacing several changes in one array.
const json = {
"Name": "Example",
"Results": [
{
"ResultId": 0,
"AnswerIds": "[1,2,33,4,5]"
},
{
"ResultId": 1,
"AnswerIds": "[2,3,4,55,6]"
}
]
}
json.Results.forEach((itm, index)=> {
const arr = JSON.parse(itm.AnswerIds);
const replacedArray = arr.map(num=>{
if(num === 2) return 7;
if(num === 3) return 8;
return num;
});
json.Results[index].AnswerIds = JSON.stringify(replacedArray);
})
console.log(json);
This is what I have done, Take the json.Results array and iterate it with a forEach loop.
You can then access the AnswerIds object of each result.
Since the AnswerIds value is a string, we first convert it to an array.
Then we loop throught that array using a map, and do the replacements.
You might want to read up on JS maps, and JS foreach, JSON.parse, JSON.stringify
SQL Server 2016 has JSON support but you did not specify your SQL version.
Using DelimitedSplit8k you can do this:
-- SAMPLE STRING
DECLARE #string VARCHAR(1000) =
'{
"Name": "Example",
"Results": [
{
"ResultId": 0,
"AnswerIds": "[1,2,33,4,5]"
},
{
"ResultId": 1,
"AnswerIds": "[2,3,4,55,6]"
}
]
}';
-- SOLUTION
SELECT NewJSON =
(
SELECT
IIF(i.pos = 0,IIF(i.pos>0 AND sub.string IN(2,3), x.x, sub.string),
IIF(s2.ItemNumber=1,'"[','')+IIF(i.pos>0 AND sub.string IN(2,3),x.x,sub.string)) +
IIF(s2.ItemNumber>LEAD(s2.ItemNumber,1) OVER (ORDER BY s.ItemNumber,s2.ItemNumber),']"',
IIF(i.pos = 0,'',','))
FROM dbo.delimitedsplit8k(REPLACE(#string,CHAR(13),''),CHAR(10)) AS s
CROSS APPLY (VALUES(CHARINDEX('"AnswerIds": "',s.item))) AS i(pos)
CROSS APPLY (VALUES(SUBSTRING(s.item, i.pos+14, 8000))) AS ss(item)
CROSS APPLY dbo.delimitedsplit8k(ss.item,IIF(i.pos=0,CHAR(0),',')) AS s2
CROSS APPLY (VALUES(IIF(i.pos=0,s.item,
REPLACE(REPLACE(s2.item,']"',''),'[','')))) AS sub(string)
CROSS APPLY (VALUES(REPLACE(REPLACE(sub.string,'2',7),'3',8))) AS x(x)
ORDER BY s.ItemNumber, s2.ItemNumber
FOR XML PATH('')
);
Returns:
{
"Name": "Example",
"Results": [
{
"ResultId": 0,
"AnswerIds": "[1,7,33,4,5]"
},
{
"ResultId": 1,
"AnswerIds": "[7,8,4,55,6]"
}
]
}
Suppose a API request fetches a users id, email address and birthday. Sample API Request below:
GET: /v1/users HTTP/1.1
Content-Type: application/json
Authorization: bearer {access_token}
For the above request, the following is the response:
{
"content": [
{
"id": 1,
"email": "random#random.com",
"birthday": "1990-01-01"
},
{
"id": 40,
"email": "random1#random1.com",
"birthday": "1990-18-10"
}
],
"last": false,
"total_elements": 2,
"total_pages": 1,
"sort": null,
"first": true,
"number_of_elements": 2,
"size": 20,
"number": 0
}
Now, what will be the test in postman to make sure that all the returned values under birthday node is greater than 1988-18-01?
I have tried the following:
pm.test("Check birthday greater than 1988-18-01", () => {
for (i = 0; i < jsonData.content.length; i++) {
var a = '1988-18-01';
pm.expect(jsonData.content[i].birthday).to.be.above(a);
}
});
But postman says: "Check birthday greater than 1988-18-01 | AssertionError: expected '1990-01-01' to be a number or a date".
So firstly, the dates need to be converted to a format that JS accepts and use the Date constructor to generate the complete date.
Next, the 'above' function in pm accepts an integer, so the date format will not be compared.
To fix this, we can convert the date to integer format by using the .getTime() function.
Lastly, it's not a good practice to declare variables inside a for loop.
Here's what you can replace your test with:
pm.test("Check birthday greater than 1988-18-01", () => {
let date,
isoFormatDate,
a = new Date('1988-01-18').getTime();
for (i = 0; i < jsonData.content.length; i++) {
date = jsonData.content[i].birthday;
isoFormatDate = new Date(date).getTime(); // Converting to integer from date format
pm.expect(isoFormatDate).to.be.above(a);
}
});
I'm trying to merge two arrays of objects by checking if the titles are the same, and if they are, then checking for which entry is newer, and discarding the older one. I have found a lot of solutions for discarding true duplicates, but how can I do this in a way where I can decided which to keep based on dates?
const a = [{
"title": "title1",
"date": "2010-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
const b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
Thanks for any tips you can provide!
Here is ES6 code to do that:
var res = Array.from([...a,...b].reduce ( (hash, v) =>
!hash.has(v.title) || hash.get(v.title).date < v.date ? hash.set(v.title, v) : hash
, new Map()), v => v[1]);
var a = [{
"title": "title1",
"date": "2010-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
var b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
var res = Array.from([...a,...b].reduce ( (hash, v) =>
!hash.has(v.title) || hash.get(v.title).date < v.date ? hash.set(v.title, v) : hash
, new Map()), v => v[1]);
console.log(res);
Explanation
First the input arrays are concatenated together into one new array with the spread operator:
[...a,...b]
Then an empty Map is created and passed as last argument to reduce:
new Map()
The reduce method calls the arrow function for each element in the concatenated array. The arrow function also receives the above mentioned map as argument (as hash).
The arrow function must return a value. That value is then passed again to subsequent call of this function (for the next element), and so we always return the map, which grows in each function call. It is, as it were, passed from one call to the next. In the last call the returned map becomes the return value of .reduce().
The arrow function itself checks if the current element's title is not yet in the map:
!hash.has(v.title)
If it is in the map already, then the next expression is also evaluated; it checks whether the date in the map entry is before the current element's date.
hash.get(v.title).date < date
If either of the above conditions is true (not in map, or with smaller date), then the map entry is (re)created with the current element as value.
? hash.set(v.title, v)
This set also returns the whole map after setting. Otherwise the map is returned unchanged:
: hash
The result of reduce() is thus a map, keyed by titles. This is really the result you need, but it is in Map format. To get it back to a normal array, the Array.from method is called on it. This changes the Map values into an array of key-value pairs (sub-arrays with 2 elements). Since we are only interested in the values, we apply a function to it:
v => v[1]
This replaces every pair with only the second value. This function is passed as second argument to Array.from which applies it to every pair.
Some remarks
This assumes that your dates are in ISO format, like in your sample: in that case the string comparison gives the correct result for determining whether one date precedes another.
The result will include also the objects that only occur in one of the two input arrays
This is easily extendible to three input arrays: just add a third one like this: [...a,...b,...c]
This runs in O(n) time, where n is the total number of objects present in the input arrays. This is because most JavaScript engines implement Map access operations like .has, .get and .put with O(1) time.
a.forEach(function(elem1,count1){
b.forEach(function(elem2,count2){
//loop trough each a frouvh each b
if(elem1.title==elem2.title){
var date1=new Date(elem1.date);
var date2=new Date(elem2.date);
if(date1.getTime()>date2.getTime()){
//elem from a is older
//delete elem from a
}else{
//elem from b is older
}
}
});
});
var a = [{
"title": "title1",
"date": "2010-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
var b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}]
function assign(a, b) {
return a.reduce((acc, itemA) => {
const {title: titleA, date: dateA} = itemA
const itemB = b.find(({title: titleB}) => titleA == titleB)
if (itemB) {
if (new Date(dateA) - new Date(itemB.date) >= 0) {
acc.push(itemA)
} else {
acc.push(itemB)
}
}
return acc
}, [])
}
You can use for loop, new Date().getTime()
var a = [{
"title": "title1",
"date": "2010-08-20T15:51:59"
}, {
"title": "title2",
"date": "2015-09-20T16:45:22"
}];
var b = [{
"title": "title1",
"date": "2015-08-20T15:51:58"
}, {
"title": "title2",
"date": "2015-09-20T16:45:21"
}];
var res = [];
for (var i = 0; i < a.length; i++) {
var curr = a[i];
for (var n = 0; n < b.length; n++) {
if (curr.title === b[n].title) {
if (new Date(curr.date).getTime() > new Date(b[n].date).getTime()) {
res.push(curr)
} else {
res.push(b[n])
}
}
}
}
console.log(res);
I have an JSON array like this
var filter_value_data = [{"Status":[{"name":"Open","id":"1"},{"name":"Pending","id":"2"},{"name":"Resolved","id":"3"},{"name":"Closed","id":"4"},{"name":"Evaluation","id":"5"}]},{"Payment Status":[{"name":"Paid","id":"10"},{"name":"UnPaid","id":"11"},{"name":"Part Paid","id":"12"}]},{"Priority":[{"name":"Low","id":"6"},{"name":"Medium","id":"7"},{"name":"High","id":"8"},{"name":"Urgent","id":"9"}]}]
I have tried filter_value_data["Status"] which is obviously wrong. How do I get the JSON elements for Status using the names like Status,Payment Status?
filter_value_data is an array (having []), so use filter_value_data[0].Status to get the first element-object with property "Status".
It is always good to format your code in order to see the hierarchy of the structures:
var filter_value_data = [
{
"Status": [
{
"name": "Open",
"id": "1"
}, {
"name": "Pending",
"id": "2"
}, ...
]
}, {
"Payment Status": [
{
"name": "Paid",
"id": "10"
}, ...
]
}, {
"Priority": [
{
"name": "Low",
"id": "6"
}, ...
]
}
];
With your current JSON you can't get the elements with the name alone.
You can get Status with filter_value_data[0]['Status'] and Payment status with filter_value_data[1]['Payment Status'].
This is because the keys are in seperate objects in the array.
In order to get them with filter_value_data['Status'] you need to change your JSON to
var filter_value_data = {
"Status":[
{"name":"Open","id":"1"},
{"name":"Pending","id":"2"},
{"name":"Resolved","id":"3"},
{"name":"Closed","id":"4"},
{"name":"Evaluation","id":"5"}
],
"Payment Status":[
{"name":"Paid","id":"10"},
{"name":"UnPaid","id":"11"},
{"name":"Part Paid","id":"12"}
],
"Priority":[
{"name":"Low","id":"6"},
{"name":"Medium","id":"7"},
{"name":"High","id":"8"},
{"name":"Urgent","id":"9"}
]
};
I wrote this on my phone so it's not as well-formatted as usual. I'll change it ASAP.
With your current JSON, created a result which might be helpful for you.
JS:
$.each(filter_value_data,function(ind,val){
var sta = val.Status; // Status Object get displayed
for(var i=0;i<sta.length;i++){
var idVal= sta[i].id;
var nameVal = sta[i].name;
Statusarray.push(idVal,nameVal);
console.log(Statusarray);
}
})
FiddleDemo
You can use below code, it will return status object
filter_value_data[0]['Status']
filter_value_data[0]['Payment Status']
to get Single value you use :
filter_value_data[0]['Status'][0]['name']
I have a valid JSON object like this:
{
"reasons": {
"options": [
{
"value": "",
"label": "Choose a reason",
"selected": true,
"requiresValidation": false
},
{
"value": "small",
"label": "Too little",
"selected": false,
"requiresValidation": false
},
{
"value": "big",
"label": "Too big",
"selected": false,
"requiresValidation": false
},
{
"value": "unsuitable",
"label": "I don't like it",
"selected": false,
"requiresValidation": true
},
{
"value": "other",
"label": "Other",
"selected": false,
"requiresValidation": true
}
]
}
}
and I have a variable which stores one value (e.g. unsuitable) of an option available in options.
How can I retrieve the value of requiresValidation field for the value stored in the variable without having to loop through all of the objects values inside options?
For instance, if the var content is other I'd like to access to requireValidation field of the object whose value is other (which is true). Is it possible?
Thank you.
You aren't really dealing with JSON here, you are dealing with a JS object. JSON is just a format for sending JS objects.
options is an array. The only way to access it is by index, which means you will have to do a search, one item at a time. There are functions, such as indexOf() which will return the first index of a value in an array, however, you have an array of objects, so that will not work in this case. (And internally, it is still doing a search).
function getReqVal(val) {
for (var item in mydata.reasons.options) {
if(item.value == val) {
return item.requiresValidation;
}
}
}
getReqVal("other");
The caveat is that this will return on the first one, so if you have more than one other, you won't get them.
If the options are indeed unique values, I would rearrange your object to be an associative array, with the keys being the "value" items, and the values being an object with the rest of the data:
{
"reasons": {
"options": {
"" : {
"label": "Seleziona una voce",
"selected": true,
"requiresValidation": false
},
"small" : {
"label": "Too little",
"selected": false,
"requiresValidation": false
},
"big" : {
"label": "Too big",
"selected": false,
"requiresValidation": false
},
"unsuitable" : {
"label": "I don't like it",
"selected": false,
"requiresValidation": true
},
"other" : {
"label": "Other",
"selected": false,
"requiresValidation": true
}
}
}
}
If you are (or could be) using underscore.js you could use the find method:
var item = _.find(myObj.reasons.options,
function(option){ return option.value == 'some value' });
Assuming you can't change the JSON structure itself (because perhaps you're getting it from an external source?), you can read it into a new object of your design per Marc B's suggestion. Ideally, this new object would let you index into your options array using the value key. Let's do that:
function MyOptions(optionsJSON) {
this.original_json = optionsJSON;
this.length = optionsJSON.reasons.options.length;
var original_options = optionsJSON.reasons.options;
for(var i = 0; i < this.length; i++)
this[original_options[i].value] = original_options[i];
}
var my_opts = new MyOptions(original_JSON);
var item_requiresValidation = my_opts["unsuitable"].requiresValidation;
console.log(item_requiresValidation); // should log "true"
The trade-off here is that your code will need to loop through the entire options array once, but after that you can index into the objects using the value key without searching. Validate with this jsfiddle.
You could use array filter. Some variation of this:
var $reasons = //Your JSON
function checkVal(element, index, array) {
return (element.value == "other");
}
var filtered = $reasons.reasons.options.filter(checkVal);
alert(filtered[0].requiresValidation);
Or jQuery grep might help you with the filter without looping: http://api.jquery.com/jQuery.grep/