Validate facts having nested maps using drools - java

I have a class Fact which is a extends java.util.HashMap class. I am passing object of this class as fact to drools.
Now an instance of fact looks like this (Map<String, Object>):
{
"key1": "value"
"attributes": [{"name": "name1", "value": "value1"},{"name": "name2", "value": "value2"},{"name": "name3", "value": "value3"}...]
"locks": [{"type": "type1", "value": "value1", "attributes": {"key_a1": "val_a1""key_a2": "val_a2"...}}]
}
Running validations on root level entries in this map is straight forward e.g. running validations on key1.
Now, I want to run some validations on attributes and locks.
For attributes, I want to ensure that all attributes which are needed are present in this map and their corresponding values are correct. So I do this in the when block:
fact: Fact(this["key1"] != null && this.containsKey("attributes"));
attributesEntries: Entry(key == "attributes") from fact.entrySet();
attributesMaps: LinkedHashMap() from attributesEntries;
fact is HashMap
attributes are of type ArrayList<LinkedHashMap<String, String>> (an id key is also added for the LinkedHashMap whose value is the value of key name only).
locks are of type ArrayList<LinkedHashMap<String, Object>>
locks have attributes of type Map<String, String>
but it is not working. When I evaluate attributesEntries it is ArrayList<LinkedHashMap> and it has all the expected values but attributesMaps comes as empty. I also tried passing filters like LinkedHashMap(key == 'key1', value == 'val1') but that also didn't work. Tried looking for solutions and none were available for this sort of structure. Whatever was available I tried to extend but didn't work.
Is this possible to achieve and if so how? Also, how do I validate value (not empty and matches a pattern) once I am able to get it from the Map.
I am new to drools and we are using 5.4.0.Final version of drools.
Also, how can I work with the next level nested Map in locks.

I once had the misfortune of working on a project where we made this same mistake and had our class extend HashMap. (Fair warning: HashMap doesn't serialize well so you're going to use a lot of extra memory.)
I'm going to assume several things about your model because you neglected to share the class definition itself.
But I'm going to assume the following, based on your example JSON:
You have added a string value ("value") with the key "key1"
You have added a List<Map<String, ?>> value (possibly a List<Fact>) with the key "locks"
You have added a List<Map<String, ?>> value (possibly a List<Fact>) with the key "attributes"
The HashMap's get(key) method will return an object value; you've already noted the special this[ key ] syntax.
From your partial rule attempt, it's not entirely clear what you're trying to do. I think you're trying to get the List<Map<String, ?>> that is saved in your map under the "attributes" key.
rule "Do something with the attributes"
when
$fact: Fact( this["key1"] != null,
$attr: this["attributes"] != null )
then
System.out.println("Found " + $attr.size() + " attributes");
end
this["attributes"] returns the value associated with the key attributes. In this case, it's a List or whatever you shoved in there. If the key doesn't exist, the null check handles that.
You also asked how you could do stuff with a child map inside one of those lists. Let's say that want to do something with the attribute that has "name": "name1" ...
rule "Do something with the 'name = name1' attribute"
when
$fact: Fact( this["key1"] != null,
$attributes: this["attributes"] != null )
$nameAttr: Map( this["name"] == "name1" ) from $attributes
then
// do something with $nameAttr
end
The pattern repeats, of course. Let's say you've shoved yet another List<Map<String, ?>> into your attribute maps:
rule "Do something with a child of 'name' attribute"
when
$fact: Fact( this["key1"] != null,
$attributes: this["attributes"] != null )
$nameAttr: Map( this["name"] == "name1",
$attrKids: this["children"] != null ) from $attributes
$childNameAttr: Map( this["name"] == "child1" ) from $attrKids
then
// etc.
end
I strongly recommend reconsidering your object model to not be Map-based. At the company I worked at where all of our projects were built against a nested Map-based model and running Drools 5.0.1, I spent significant time and effort upgrading parts of it to Drools 7 and a proper model that passed in just the data we needed. It saved a ton of resources and ended up being much faster.

Related

Serialize a hashmap to a json in a certain feilds in JAVA

I need to serialize a map to a json in a certain order.
This is the map
HashMap<String, String> dataMap = {
"CompanyCode": "4",
"EntyyCode": "2002296",
"SubEntityCode": "000",
"ContractNumber": "52504467115",
"Progressive Contract": "0",
"DocumentNumber": "200003333494028",
"LogonUserName": "AR333",
"Progressive Title": "0"
}
This is the json model I would like:
{
"Policy": {
"ContractNumber": "52504467115",
"ProgressiveContract": "0"
},
"Title": {
"LogonUserName": "AR333",
"ProgressiveTitle": "0"
},
"BusinessChannel": {
"CompanyCode": "4",
"EntyyCode": "2002296",
"SubEntityCode": "000"
},
"Document": {
"DocumentNumber": "200003333494028"
}
}
I need to convert this map into a JSON string. I know that this can be done using Jackson as below:
new ObjectMapper().writeValueAsString(map);
How do I do this using Jackson? Or is there any other way to do this in Java?
Thank you
First of all, the solution you request contains a second problem: partition. Not only must the items contain a particular order, but they must also somehow be divided over different categories. In Java, these categories usually correspond to their own classes or, since recently, records. Then the top level class (corresponding to the unnamed outer object of the JSON) determines ordering, as so (the name Contract is my choice):
record Contract(
Policy policy,
Title title,
BusinessChannel businessChannel,
Document document )
{
}
with each of the properties of Contract having their own class, e.g.:
record Policy( String contractNumber, int progressiveContract )
etc.
Serializing Contract then recursively serializes each of its parameters, with the required outcome as the result.
This would be the 'standard' way.
So, since you start with a HashMap, which by contract offers no guarantee of ordering, let alone an easy way to partition its contents into sub-objects, you could try two things:
Rethink the use of a map. Switching to the class structure takes care of the structure automatically.
Manually stream and convert the values in order (or use e.g. a TreeMap with custom Comparator) and then partition the values themselves. This probably requires more work than a map saves.

How to remove the json key value with JSonPath in java

I know it could be a duplicate, but still posting my question as i could not find the exact answer what i am looking for. I am having an json object (or string) like below.
String str = "{
"status" : {
"timestamp" : "2020-04-30T01:00:00 000Z"
"error" : 0,
"error_message" : null,
"execution" : "completed"
}
}
";
I will get the a same kind of response from my REST API testing, but after each call the 'timestamp' key will be having a dynamic date and time value with the time respect to the call made. And here i compare my expect json with the actual json as a whole sting comparison using JSONAssert. As the timestamp value is different it always fails for me.
So my question is before i do any comparison, i would like to remove the 'timestamp' key and its value from json to compare and so it will pass my case. I tried by using JsonPath also, but did not works. Any help on this please?
JSONAssert allow you to make a customized comparator while doing asserts [1].
In your case it's easy as:
JSONAssert.assertEquals(expectedJson,
actualJson,
new CustomComparator(JSONCompareMode.LENIENT,
skips("status.timestamp","another.json.path", ...)));
private static Customization[] skips(String... jsonPaths) {
return Arrays.stream(jsonPaths)
.map(jsonPath -> Customization.customization(jsonPath, (o1, o2) -> true))
.toArray(Customization[]::new);
}
Here we are defining CustomComparator, with customization which takes JSONPath (status.timestamp) and takes a ValueMatcher (lambda) which compares two values for that specific JSONPath.
In our case we will always return true, which will effectively skips the value (no matter with what we are comparing that value it's always true).
Edit: As you can see CustomComparator's constructor takes varargs of Customizations, hence you can provide more than one field to be ignore from comparison.
[1] http://jsonassert.skyscreamer.org/apidocs/org/skyscreamer/jsonassert/Customization.html
[2] http://jsonassert.skyscreamer.org/apidocs/org/skyscreamer/jsonassert/comparator/CustomComparator.html

Map inside a Map with Jackson with JSF

I'm using Jackson to read/write datas from/into json files and I have an issue with the User POJO. It has a Map wich is supposed to be the ways to contact the User (so it can have from 0 to 7, depending on the Enum). I want to be able to put ways to contact using a form in JSF.
I tried something like value="#{config.user.contacts[EMAIL_PRO]}"
where of course EMAIL_PRO is an Enum (later, the user should be able to chose the Enum himself, but right now I try simple).
But when I do so, the error is
Null key for a Map not allowed in JSON
wich I understand, 'cause my debug says that the value returned is{null = null}. Now first question : since the map is empty, is JSF supposed to work simply like that ? The key "EMAIL_PRO" doesnt exists yet, but shouldn't JSF make the work done for me, and put right value with the key ?
The other question is much more about Jackson and Maps. As I said, my POJO User contains a Map, and the json file is a Map himself (containing multiple users).
Is it really possible to write a Map into this file using Jackson where the Map is Map<String, Object> and the Object contains a Map<Enum, Object> ? And if yes, how ?
Thanks for the help
PS: I cannot change either my APIs or my POJOs.
I think this is a repeated post, see How to convert hashmap to JSON object in Java
And as it says on one of the responses:
Map<String, Object> data = new HashMap<String, Object>();
data.put( "name", "Mars" );
data.put( "age", 32 );
data.put( "city", "NY" );
JSONObject json = new JSONObject();
json.putAll( data );
System.out.printf( "JSON: %s", json.toString(2) );
output:
JSON: {
"age": 32,
"name": "Mars",
"city": "NY"
}
You can also try to use Google's GSON.Google's GSON is the best library available to convert Java Objects into their JSON representation

How to use distinct/aggregate to get all fields that match several queries

I just learned how to use distinct.
What I do is create a BasicDBObject, put as query parameter to distinct what I want to be equal, and as field parameter what I want returned.
Now I want to do something similar, but with several queries. That meaning, I want the query to match several keys of the document (id and date have to be the same as the input I get), and return what sessions match that in the collection.
I tried doing something similar to find, but for distinct, where you add with append() or put() more fields to the query parameter.
This syntax does not seem to work and I found no one using similar code, so I guess it's not possible.
I've found the aggregate() method, but it seems to be used to match several FIELDS, not queries. Explanation with code:
array.put(coll.distinct(field, query));
I want that query parameter to have several keys, so that all fields match my input, and I find unique values of field that match both (or as many) keys in query.
Thanks in advance!
Edit:
Basics: MongoDB 3.2.2
Data manipulation:
"Session" : "value1", "car" : "carNumber", "date" : "20130321"
I have a very large collection with a number of documents that have, among other keys, this ones. I want, given a car and a number, get every UNIQUE session value, and return it as a json (for which, so far, I put the values into an array, and transform into json).
driver/framework specific question: I do not know to query this in mongodb shell. I know to use distinct, but not aggregators.
There are multiple parts in your question. I would like to answer the last part which is highlighted in bold. The solution is written in Java as the thread is tagged as Java.
The below code would give you the distinct session values for a car and car number. You can change the filter accordingly for your requirement.
The below code satisfies the basic distinct concept for your requirement. I assume that you can add code to result set into JSON (you can use Jackson or Gson libs for generating JSON).
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
public class MongoReadDistinct {
public static void main(String[] args) {
MongoClient client = new MongoClient();
MongoDatabase database = client.getDatabase("cars");
MongoCursor<String> mongoCursorIds = database
.getCollection("sessions").distinct("Session",
Filters.and(Filters.eq("car", "Nisson_Note"), Filters.eq("carnumber", 123)), String.class)
.iterator();
while (mongoCursorIds.hasNext()) {
System.out.println(mongoCursorIds.next());
//You can convert the result to JSON
}
}
}
Sample Data:-
/* 1 */
{
"_id" : ObjectId("576a6860d317ab85059c76d4"),
"Session" : "value1",
"car" : "Nisson_Note",
"carnumber" : 123,
"date" : "20130321"
}
/* 2 */
{
"_id" : ObjectId("576a6896d317ab85059c76d5"),
"Session" : "value2",
"car" : "Nisson_Note",
"carnumber" : 123,
"date" : "20130321"
}
/* 3 */
{
"_id" : ObjectId("576a68b4d317ab85059c76d6"),
"Session" : "value2",
"car" : "Nisson_Note",
"carnumber" : 123,
"date" : "20140321"
}
Output:-
value1
value2
Well, to answer my own question, it is actually possible to have several queries in distinct method, it can be done both in mongodb shell and in java driver (unfortunately I did not get the other answer to work, not that is wrong, I just didn't manage).
So for mongodb shell (I include it because I didn't know to do this, either, which was part of the problem):
db.colectionLocalCC.distinct("Session", {date: "20130303", Car: "55"})
And for mongodb:
BasicDBObject query = new BasicDBObject();
query.put("date", date);
query.put("car",car);
String fields = "Session";
array.put(coll.distinct(fields, query));

Do JSON keys need to be unique? [duplicate]

This question already has answers here:
Does JSON syntax allow duplicate keys in an object?
(12 answers)
Closed 6 years ago.
The following question is related to a question that I had asked earlier: Help parsing simple JSON (using JSON for JAVA ME)
Do JSON keys need to be unique? For example, I was having trouble parsing the following XML (with JSON ME):
{
"name" : "JACK",
"name" : "JILL",
"name" : "JOHN",
"name" : "JENNY",
"name" : "JAMES",
"name" : "JIM"
}
And, apparently, its because the keys must be unique. I'm just wondering if thats true in all cases or not. For example, if I were using something other than JSON ME, would I be able to parse all of these names?
Thanks.
There is no "error" if you use more than one key with the same name, but in JSON, the last key with the same name is the one that is going to be used.
In your case, the key "name" would be better to contain an array as it's value, instead of having a number of keys "name". It doesn't make much sense the same object or "thing" to have two names, or two of the same properties that are in conflict.
E.g.:
{
"name" : [ "JOHN", "JACK", "...", ... ]
}
From RFC 4627:
An object structure is represented as a pair of curly brackets
surrounding zero or more name/value pairs (or members). A name is a
string. A single colon comes after each name, separating the name
from the value. A single comma separates a value from a following
name. The names within an object SHOULD be unique.
jQuery is able to parse it. But if you try to access it, it's just giving back the last value.
Check out
http://jsfiddle.net/MQmM4/2/
So, it's parsable, I guess, but the value gets overridden if you use the same key.
here is a possible solution using array. just use array index
the_json_array.getJSONObject(0);
{"nameList":
[{"name" : "JACK"},
{"name" : "JILL"},
{"name" : "JOHN"},
{"name" : "JENNY"},
{"name" : "JAMES"},
{"name" : "JIM"}]}
A JSON Object looks like the following
public JSONObject(Map<?, ?> map) {
this.map = new HashMap<String, Object>();
if (map != null) {
for (final Entry<?, ?> e : map.entrySet()) {
final Object value = e.getValue();
if (value != null) {
this.map.put(String.valueOf(e.getKey()), wrap(value));
}
}
}
}
A JSON Object is basically a hashmap containing key value pair.
This is why you are getting overwritten each time. To avoid this
Either you must have unique key values
Or you should wrap the key value pair as individual objects into an array
Have a look at this JSON Object java implementation to know in depth about JSON.

Categories