Is it possible to do expressions/calculations in json? - javascript

I am using the wonderful json-server as the backend of my application and it's really useful for hitting custom endpoints to retrieve some data. but what would be super useful if it allowed me to do calculations/expression so that i can mimic that backend behaviour too.
take this data structure for example
{
"products": [
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "false",
"coupons": [
"daw124qdw",
"a1212cxn"
]
}
]
}
I would like some way of saying something like "discountPrice": couponApplied ? price * couponDiscount
that's just me pseudo coding. but I would like to do something where I can calculate the price on the fly. or when I make a request it does a calculation and returns me the calculated data (like a backend app would)
I understand I can make a request, apply the coupon and render that new price. or even make a post request and change the price. but that is all done client side. is there any way to do this either with json or json-server or any other solutions. if that makes sense?

JSON means JavaScript Object Notation and is data structure, and does not have any preprocessor for it. You can use any JSON parser and append/change values that you need dynamically.
So in short: no, there is no possibility to add dynamic values

No, you'll not be able to do computations inside json. The data would need to be mutated elsewhere and then sent.

No, it isn't possible to do math or any kind of expression in JSON as JSON is just a data structure format, and not a programming language.
You will need to load the JSON data in using a programming language of your choice, at which point you can then manipulate it.
For example, since you mention javascript a simple Node program as an example..
//It would be better to use the FileSystem API, but for simplicity for this example, I'm using require
var json = require('./myjson.json');
var product = json.products[0];
//Since the dataset has "false", this if will handle both "false" (string) and false (boolean) values. The value should really be boolean if possible
product.discountPrice = product.couponApplied && product.couponApplied !== "false" ? product.price * couponDiscount : null;

If you are trying to create logic dynamically, like user creates some logic, and you want to save it into DB and later apply it somewhere, these might be useful:
An Excel like formula parser + a bit of JS codes to put data into formulas.
JSONLogic
MongoDB aggregation pipeline operations are being used in an Array of Object. Same behavior can be used inside JSON object, but you need an implementation to take care of it, something like Mingo. Check arithmetic sum for example. This JS implementation might help.
In your example, using formula parser could be like:
const response = {
"products": [
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "false",
"coupons": [
"daw124qdw",
"a1212cxn"
]
},
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "true",
"couponDiscount": 0.2,
"coupons": [
"daw124qdw",
"a1212cxn"
]
}
],
formulaFields: {
"discountPrice": 'IF("{couponApplied}"="true", {price} * {couponDiscount}, "")', // excel standard formula, with {variable} as product field keys
}
}
const productsWithValues = response.products.map((product)=>{
const productWithValues = { ...product };
for (const field in response.formulaFields){
const formula = response.formulaFields[field].replace(/\{([^\}]+)\}/g, (_, key) => product[key])
const parser = new Parser();
const { result } = parser.parse(formula);
productWithValues[field] = result;
}
return productWithValues;
})
console.log(productsWithValues)
Output:
[
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "false",
"coupons": ["daw124qdw", "a1212cxn"],
"discountPrice": null
},
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "true",
"couponDiscount": 0.2,
"coupons": ["daw124qdw", "a1212cxn"],
"discountPrice": 8
}
]

JSON doesn't support this, but if you turn it into a Javascript object, you could do something like this:
var obj = JSON.parse(
`{
"products": [
{
"name": "football",
"id": "SPO-001",
"category": "sport",
"price": 40,
"couponApplied": "true",
"couponDiscount": 0.5,
"coupons": [
"daw124qdw",
"a1212cxn"
]
}
]
}`).products[0];
Object.defineProperty(obj,"discountPrice",{
get:function(){
return (this.couponApplied==="true"||this.couponApplied===true) ? this.price*this.couponDiscount : this.price;
},
set:function(){
return;
}
});
console.log(obj.discountPrice);
This uses an accessor descriptor to define an object property that depends on the values of the other object properties.

Note that json-server allow you to add custom middleware. So you could write something like this:
const updateProduct = (p) => ({
...p,
discountPrice: p.couponApplied ? p.price * p.couponDiscount : p.price
})
const transform = ({products, ...rest}) => ({
...rest,
products: products.map(updateProduct)
})
const modify = (req, res, next) => {
if (req.path !== '/my-route') return next();
res.body = JSON.stringify(transform(JSON.parse(res.body)))
next();
}
// dummy call -- would really be handled by json-server/express
(() => {
const req = {path: '/my-route'};
const res = {body: `{"products":[{"name":"football","id":"SPO-001","category":"sport","price":40,"couponApplied":false,"coupons":["daw124qdw","a1212cxn"]},{"name":"helmet","id":"SPO-042","category":"sport","price":50,"couponApplied":true,"couponDiscount":0.75,"coupons":["foobarbaz"]}]}`}
const next = () => {console.log(JSON.parse(res.body))}
modify(req, res, next)
})()

Related

Make CreateQueryBuilder return nested object instead of one flat object

I'm using Typescript with TypeORM. Using CreateQueryBuilder, I want to receive a nested object. Instead I'm receiving a single flat object as represented in block number two. How can I fix this?
const x = await getConnection()
.createQueryBuilder()
.select(['reportHead', 'order', 'workOrder'])
.from('report_head', 'reportHead')
.innerJoin('reportHead.workOrder', 'workOrder')
.innerJoin('workOrder.order', 'order')
.where(`order.customer.id = :customerId`, { customerId: req.user.customer.id })
.execute();
How can I avoid the data looking like this:
{
"reportHead_id": "asd",
"reportHead_number": "123",
"workOrder_id": "dsa",
"workOrder_status: "OK",
"order_id": "sda",
"order_whatev": "ks"
}
but rather have a neste object like this:
{
"reportHead": {
"id": ...
},
"workOrder": {
"id": ...
},
"order": {
"id": ...
}
}
The solution was to not use .execute(), but rather .getMany().

Looping JSON through nested array for to see if all values are equal

Basically I want to see how many people purchase the same vendor and how many purchased different brands together. I already know the brands beforehand
I would like to loop through all the objects in this JSON response and if the "vendor" value for all "line_items" are the same then console log "these orders have same brands"+ order number. If they are different console log "these orders have different brands" + order number
{
"orders": [
{
"email": "bob.norman#hostmail.com",
"order_number": 1001,
"line_items": [
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
},
{
"email": "john.candy#hostmail.com",
"order_number": 1002,
"line_items": [
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Apple"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
}
]
}
Let's start with a function that checks if all elements of an array are equal:
const allEqual = ([x, ...ys]) =>
ys.every(y => y === x);
console.log(
allEqual([1, 2, 2]), // false
allEqual([1, 1, 1]), // true
allEqual([1]), // true
allEqual([]) // true
);
With this out of the way, we can check if a list of item is from a single vendor like so:
const itemsAreSameVendor = items =>
allEqual(items.map(({ vendor }) => vendor));
To check if an order is a "single vendor order", we write:
const orderIsSingleVendor = ({ line_items }) =>
itemsAreSameVendor(line_items);
Now, finding the single orders is a matter of:
const singleCount = orderData.orders.filter(orderIsSingleVendor).length;
const multiCount = orderData.orders.length - singleCount;
Or, you can use reduce to make two handy groups:
const allEqual = ([x, ...ys]) =>
ys.every(y => y === x);
const itemsAreSameVendor = items =>
allEqual(items.map(({ vendor }) => vendor));
const orderIsSingleVendor = ({ line_items }) => itemsAreSameVendor(line_items);
console.log(
orderData().orders.reduce(
({ singleVendorOrders, multiVendorOrders }, order) => {
// Check if this order contains multiple vendors
const orderType = orderIsSingleVendor(order)
? singleVendorOrders
: multiVendorOrders;
// Push to the right list
orderType.push(order.order_number);
return { singleVendorOrders, multiVendorOrders };
},
// Format of the result
{ singleVendorOrders: [], multiVendorOrders: [] }
)
)
function orderData() {
return {
"orders": [{
"email": "bob.norman#hostmail.com",
"order_number": 1001,
"line_items": [{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
},
{
"email": "john.candy#hostmail.com",
"order_number": 1002,
"line_items": [{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Apple"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
}
]
};
}
P.S.: You haven't really shown what exact part you need help with... If you add your own attempt to the question, you'll get better answers and people will be able to point to existing answers solving similar problems.
Loading the JSON into your code
First you have to load the JSON. If you want to load it from a local archive you'd have to load it creating a function for this. Since loading archives is an asynchronous action, you need to pass a callback to tell the function what to do with that data after it finishes loading. In that callback you'll tell your program what to do with the JSON.
Here's a function implementation to do this. You could also make it for general use and give it a param to especify the file location that then replaces the 'my_data.json' part.
If you want to get the JSON using a GET call to an API or your own backend using Node.js (since the tags of the question say node.js) you can use Node.js's HTTPS module .
With this module you'll want to use especifically this part which is a GET passing either options or an URL with a callback.
Handling the JSON
Once you have your JSON ready and stored in a variable you'll need to parse it first since it'll come as an string.
var parsedJSON = JSON.parse(myJson); //Where myJson is a variable where it is stored
Then you'll have your Javascript object with nested array of objects ready to go.
Now onto the loop:
parsedJSON.orders.forEach((order) => {
let areEqual = order['line_items'].reduce((result, item, currentIndex, items) => {
if (currentIndex === 0) {
return true;
}
if (currentIndex - 1 < items.length) {
const previousVendor = items[currentIndex - 1].vendor; //Previous one
if (y.vendor !== previousVendor) //We compare them
return result && false; //Since they are different we use the logic operator && with false so this is enough to say that there is an instance where there are at least two different
}
return result && true; // <= If they are equal. We can just return acc aswell
});
if (areEqual) console.log(order['order_number']);
});
If you want this to work for IE11 and less be aware that you can't use arrow functions like I did. Just change them for regular function expressions. Here's more info about the reduce function from Javascript array's

RethinkDB: Javascript - How to deleted nested objects

I'm having a rather large amount of difficulty with trying to remove nested objects from my table, without accidentally deleting all my data in the process (happened three times now, thank god I made copies).
My Object:
{
"value1": thing,
"value2": thing,
"value3": thing,
"roles": {
"1": {
"name": "Dave",
"id": "1"
},
"2": {
"name": "Jeff",
"id": "2"
},
"3": {
"name": "Rick",
"id": "3"
},
"4": {
"name": "Red",
"id": "4"
}
}
}`
I've tried a number of rethink queries, but none have worked thus far. It should be noted that 1, 2, 3, & 4 are variables that can have any amount of numbers, and thus my query must reflect that.
Some attempted queries:
function removeRole(id, roleName) {
let role = `${roleName}`
return this.r.table('guilds').get(id).replace(function(s){
return s.without({roles : {[role] : { "name": role }}})
})
}
function removeRole(id, roleName) {
return this.r.table('guilds').getAll(id).filter(this.r.replace(this.r.row.without(roleName))).run()
}
function removeRole(id, roleName) {
return this.r.table('guilds').get(id)('roles')(roleName).delete()
}
Any assistance is greatly appreciated, and if the question has issues, please let me know. Still rather new to this so feedback is appreciated.
I'm not sure if I understood your intention, but the following query seems to do what you're trying to accomplish:
r.db('test')
.table('test')
.get(id)
.replace((doc) => {
// This expression makes sure that we delete the specified keys only
const roleKeys = doc
.getField('roles')
.values()
// Make sure we have a role name is in the names array
.filter(role => r.expr(names).contains(role.getField('name')))
// This is a bit tricky, and I believe I implemented this in a not efficient
// way probably missing a first-class RethinkDB expression that supports
// such a case out of box. Since we are going to delete by nested dynamic
// ids, RethinkDB requires special syntax to denote nested ids:
// {roles: {ID_1: true, ID_2: true}}
// Well, this is just a JavaScript syntax workaround, so we're building
// such an object dynamically using fold.
.fold({}, (acc, role) => acc.merge(r.object(role.getField('id'), true)));
return doc.without({roles: roleKeys});
})
For example, if names is an array, say ['Jeff', 'Rick'], the nested roleKeys expession will be dynamically evaluated into:
{2: true, 3: true}
that is merged into the roles selector, and the above query will transform the document as follows:
{
"value1": ...,
"value2": ...,
"value3": ...,
"roles": {
"1": {"name": "Dave", "id": "1"},
"4": {"name": "Red", "id": "4"}
}
}

How to extract multiple hashtags from a JSON object?

I am trying to extract "animal" and "fish" hashtags from the JSON object below. I know how to extract the first instance named "animal", but I have no idea how to extract both instances. I was thinking to use a loop, but unsure where to start with it. Please advise.
data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},
{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":
[{"screen_name":"test241","name":"Test
Dude","id":4999095,"id_str":"489996095","indices":[30,1111]},
{"screen_name":"test","name":"test","id":11999991,
"id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
i = 0;
obj = JSON.parse(data);
console.log(obj.hashtags[i].text);
}
showHashtag(data);
Use Array.prototype.filter():
let data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":[{"screen_name":"test241","name":"Test Dude","id":4999095,"id_str":"489996095","indices":[30,1111]}, {"screen_name":"test","name":"test","id":11999991, "id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
return JSON.parse(data).hashtags.filter(e => /animal|fish/i.test(e.text))
}
console.log(showHashtag(data));
To make the function reusable, in case you want to find other "hashtags", you could pass an array like so:
function showHashtag(data, tags){
let r = new RegExp(tags.join("|"), "i");
return JSON.parse(data).hashtags.filter(e => r.test(e.text))
}
console.log(showHashtag(data, ['animal', 'fish']));
To get only the text property, just chain map()
console.log(showHashtag(data, ['animal', 'fish']).map(e => e.text));
or in the function
return JSON.parse(data).hashtags
.filter(e => /animal|fish/i.test(e.text))
.map(e => e.text);
EDIT:
I don't really get why you would filter by animal and fish if all you want is an array with ['animal', 'fish']. To only get the objects that have a text property, again, use filter, but like this
let data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":[{"screen_name":"test241","name":"Test Dude","id":4999095,"id_str":"489996095","indices":[30,1111]}, {"screen_name":"test","name":"test","id":11999991, "id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
return JSON.parse(data).hashtags
.filter(e => e.text)
.map(e => e.text);
}
console.log(showHashtag(data));
For me, Lodash can be of great use here, which have different functions in terms of collections. For your case i'd use _.find function to help check the array and get any of the tags with the creteria passed in as second argument like so:
.find(collection, [predicate=.identity], [fromIndex=0])
source npm package
Iterates over elements of collection, returning the first element
predicate returns truthy for. The predicate is invoked with three
arguments: (value, index|key, collection).
with your case this should work
var data = '{ "hashtags": [ { "text": "animal", "indices": [ 5110, 1521 ] }, { "text": "Fish", "indices": [ 122, 142 ] } ], "symbols": [], "user_mentions": [ { "screen_name": "test241", "name": "Test \n Dude", "id": 4999095, "id_str": "489996095", "indices": [ 30, 1111 ] }, { "screen_name": "test", "name": "test", "id": 11999991, "id_str": "1999990", "indices": [ 11, 11 ] } ], "urls": [] }';
var obj = JSON.parse(data);
_.find(obj.hashtags, { 'text': 'animal' });
// => { "text": "animal", "indices": [ 5110, 1521 ] }
For simple parsing like this one, I would use the plain old obj.forEach() method, it is more readable and easy to understand, especially for javascript beginner.
obj = JSON.parse(data).hashtags;
obj.forEach(function(element) {
console.log(element['text']);
});

Angular - Convert Jackson output to JSON

The server I'm working with changed the REST format from plain JSON:
{
"removedVertices": [
{
"id": "1",
"info": {
"host": "myhost",
"port": "1111"
},
"name": "Roy",
"type": "Worker"
}
],
"id": "2",
"time": 1481183401573
}
To Jackson format:
{
"removedVertices": [
"java.util.ArrayList",
[
{
"id": "1",
"info": [
"java.util.HashMap",
{
"host": "myhost",
"port": "1111"
}
]
"name": "Roy",
"type": "Worker",
}
]
"id": "2",
"time": 1482392323858
}
How can I parse it the way it was before in Angular/Javascript?
Assuming only arrays are affected, I would use underscore.js and write a recursive function to remove the Jackson type.
function jackson2json(input) {
return _.mapObject(input, function(val, key) {
if (_.isArray(val) && val.length > 1) {
// discard the Jackson type and keep the 2nd element of the array
return val[1];
}
else if (_.isObject(val)) {
// apply the transformation recursively
return jackson2json(val);
}
else {
// keep the value unchanged (i.e. primitive types)
return val;
}
});
}
If the api should be restful, then the server should not return none plain json results. I think the server site need to fix that.
I think it is because the server enabled the Polymorphic Type Handling feature.
Read Jackson Default Typing for object containing a field of Map and JacksonPolymorphicDeserialization.
Disable the feature and you will get result identical to plain json.
The main difference i see is that in arrays you have an additional string element at index 0.
If you always get the same structure you can do like this:
function jacksonToJson(jackson) {
jackson.removedVertices.splice(0, 1);
jackson.removedVertices.forEach((rmVert) => {
rmVert.info.splice(0, 1);
});
return jackson;
}

Categories

Resources