I would like to convert json to a string "filter" format and visa versa.
Json Format
{
"condition": "and",
"rules": [
{
"field": "Name",
"operator": "=",
"value": "Jack"
},
{
"field": "UserId",
"operator": "=",
"value": 2
},
{
"field": "Surname",
"operator": "=",
"value": "Rose"
},
{
"condition": "or",
"rules": [
{
"field": "Code",
"operator": "=",
"value": "X"
},
{
"field": "Type",
"operator": "=",
"value": "Z"
}
]
}
]
};
String Filter Format
[
["Name","=", "Jack"],
["and"],
["UserId", "=", 2],
["and"],
["Surname","=", "Rose"]
,
["and"],
(
["Code","=","X"],
["or"],
["Type","=","Z"]
)
]
At the moment I have the following js code which outputs something that is halfway there, but I'm missing the "and" and "or" conditions which need to come between the respective fields as well as the "(" ")" brackets around to group the fields.
I need to be able to convert from json to the "filter" format and back again.
This is the code I have so far: query in this case is the json I posted above.
const query={condition:"and",rules:[{field:"Name",operator:"=",value:"Jack"},{field:"UserId",operator:"=",value:2},{field:"Surname",operator:"=",value:"Rose"},{condition:"or",rules:[{field:"Code",operator:"=",value:"X"},{field:"Type",operator:"=",value:"Z"}]}]};
const parseRule = (rule) => {
if (rule.rules) {
return rule.rules.map(r=> parseRule(r));
}
return [rule.field, rule.operator, rule.value];
};
let filterFormat = parseRule(query);
console.log(filterFormat);
I'm stuck here. I almost need something like a join for arrays where the condition can be added as an array between the other arrays, without losing the arrays like you would with a normal join.
-
3I've created a Stack Snippet for you. This makes it easier for other user to debug and see the results. Please update the snippet if anything is wrong.StackByMe– StackByMe2021年07月15日 06:57:46 +00:00Commented Jul 15, 2021 at 6:57
-
Creating your string format probably won't be too difficult. Parsing it back into a JS object will be very hardPhil– Phil2021年07月15日 07:45:44 +00:00Commented Jul 15, 2021 at 7:45
-
2The string format you're trying to get is not native to javascript. That means you need your own parser/renderer. Which might be some work. Which implies the question, do you want it that way for some arbitrary reason, or because it's a fixed requirement? If it's just aesthetics, sticking to json might be the better option.Yoshi– Yoshi2021年07月15日 07:46:00 +00:00Commented Jul 15, 2021 at 7:46
-
@Terry they're not at the same level. Note the parentheses in the string format. Seems odd though, I would have wrapped the sub "or" condition in a groupPhil– Phil2021年07月15日 07:47:36 +00:00Commented Jul 15, 2021 at 7:47
-
@Phil, thanks for that spot. I had the parentheses wrong. It should be around the "or"Bracher– Bracher2021年07月15日 08:35:05 +00:00Commented Jul 15, 2021 at 8:35
1 Answer 1
The part I think you already succeeded at is going from the rule object tree to the nested array format. I wrote it using 3 main functions:
childToArrayonly checks whether a node is aconditionor aruleand forwards to the right parser functionruleToArraytakes the object and transforms it in to a tuple-like arrayconditionToArraymaps all nested children and interweaves them with theoperator
For interweaving, I use a utility method that basically does this:
(a, [1, 2, 3]) -> [1, a, 2, a, 3]
const childToArray = child => "condition" in child
? conditionToArray(child)
: ruleToArray(child);
const ruleToArray = ({ field, operator, value }) => [field, operator, value];
const conditionToArray = ({ condition, rules }) =>
interweave(
[ condition ],
rules.map(childToArray)
)
This array format might be slightly different from your requirement, but it is "lossless", which means we can translate it back in to the tree-like object!
A similar approach:
- Check if an array represents a
rule(values will be strings), or acondition(values will be arrays) - If it's a rule, transform the tuple to an object with named keys
- If it's a condition, extract the
conditionand parse the nested arrays
To untangle our [1, a, 2, a, 3] type arrays, I wrote a utility that does:
([1, a, 2, a, 3]) -> [ a, [ 1, 2, 3] ]
const buildTree = arr => {
const isCondition = Array.isArray(arr[0]);
return isCondition
? conditionToObject(untangle(arr))
: ruleToObject(arr);
}
const conditionToObject = ([ [ condition ], children ]) => ({
condition,
rules: children.map(buildTree)
});
const ruleToObject = ([ field, operator, value ]) => ({
field, operator, value
});
Putting it all together with your test data:
// Transforming object trees to nested arrays
const childToArray = child => "condition" in child
? conditionToArray(child)
: ruleToArray(child);
const ruleToArray = ({ field, operator, value }) => [field, operator, value]
const conditionToArray = ({ condition, rules }) =>
interweave(
[ condition ],
rules.map(childToArray)
)
// Transforming nested arrays to object trees
const buildTree = arr => {
const isCondition = Array.isArray(arr[0]);
return isCondition
? conditionToObject(untangle(arr))
: ruleToObject(arr);
}
const conditionToObject = ([ [ condition ], children ]) => ({
condition,
rules: children.map(buildTree)
});
const ruleToObject = ([ field, operator, value ]) => ({
field, operator, value
});
// App:
// 1) To array
const arrayOutput = childToArray(getInput());
// 2) Back to object
const objectOutput = buildTree(arrayOutput);
// 3) Log output
console.log("Array output");
console.log(JSON.stringify(arrayOutput));
console.log("Object output");
console.log(objectOutput);
// 4) Verify equality
console.log(
"inp obj -> arr -> out obj == inp obj:",
JSON.stringify(objectOutput) === JSON.stringify(getInput())
);
// 5) arrayOutput with custom toString
const arrayOutputToString = arr => {
const isCond = Array.isArray(arr[0]);
return isCond
? `(${arr.map(arrayOutputToString)})`
: JSON.stringify(arr)
}
console.log(
arrayOutputToString(arrayOutput)
)
function getInput() {
return {
"condition": "and",
"rules": [{
"field": "Name",
"operator": "=",
"value": "Jack"
},
{
"field": "UserId",
"operator": "=",
"value": 2
},
{
"field": "Surname",
"operator": "=",
"value": "Rose"
},
{
"condition": "or",
"rules": [{
"field": "Code",
"operator": "=",
"value": "X"
},
{
"field": "Type",
"operator": "=",
"value": "Z"
}
]
}
]
}
};
<script>
// Utils
const interweave = (y, xs) => xs.flatMap(
(x, i) => i === xs.length - 1 ? [x] : [x, y]
);
const untangle = xs => {
const wovenEl = xs[1];
const els = [];
for (let i = 0; i < xs.length; i += 2) {
els.push(xs[i]);
}
return [ wovenEl, els ];
}
</script>
4 Comments
toString function to use () around conditions. To be consistent however, I think you also need the () around the outermost part of the filter. In your example, that part has [].