Parsing JSONObject but running into NullPointer exception - java

I am parsing a JSONObject which I extract from a Mongo DB. The number values map to java.lang.number objects according to the documentation, However, I get an error when I try to make the following declarations, specifically in the first System.out.println(value.floatVlaue()).
//For data
int counter = 0;
float score = 0;
Number value = 0;
while(cur.hasNext()){
JSONObject jsonObj = (JSONObject) JSONValue.parse(cur.next().toString());
metricsIterator = ((JSONObject)((JSONObject)((JSONObject)((JSONObject)jsonObj.get("level_1")).get("level_2")).get("level_3")).get("metrics")).keySet().iterator();
while(metricsIterator.hasNext()){
System.out.println(metricsIterator.next().toString());
value = (Number) jsonObj.get(metricsIterator.next());
System.out.println(value.floatValue());
System.out.println(value.toString());
someLogic(value.doubleValue(), ((Number)jsonObj.get("long")).intValue(), start, end, days);
}
}
I think the problem line definitely comes from the value = (Number) jsonObj.get(metrics Iterator.next())
the Json im parsing looks like this
{ "_id" : "9223370663181869423_cnn_1" ,
"long" : 1373672925000 ,
"level_1" :
{ "level_2" :
{ "level_3level" :
{ "metrics" :
{"key1" : 0.003333333333333334 ,
"key2" : 0.005833333333333334 ,
"key3" : 0.005833333333333334 ,
"key4" : 0.009166666666666667 ,
"key5" : 0.1391666666666667 ,
"key6" : 0.1241666666666667 ,
"key7" : 0.01666666666666667
}
}
}
}
}
Thanks so much for any help at all!

Iterators can only be traversed once. This means that once you do metricsIterator.next() in System.out.println(metricsIterator.next().toString());, you end up using up that value and when you call .next() again in value = (Number) jsonObj.get(metricsIterator.next()); you get the value after the one that you are expecting, in other words you skip a value.
As a result when you hit the last value, the next value is null which results in the null pointer.
Try removing the sysout before assignment to value, it should solve the nullpointer.

you are doing twice the metricsIterator.next()
first: System.out.println(metricsIterator.next().toString());
second: value = (Number) jsonObj.get(metricsIterator.next());
then you get to the end without checking if hasNext()

Related

Compare with null Date in range

#Document
public class Product extends Item {
private Date onlineDate;
private Date offlineDate;
}
Data
Product {
onlineDate : some date
offlineDate : null
}
below query return 0 hits
query.addCriteria(Criteria.where("onlineDate").lte(date).and("offlineDate").gte(date))
but below query returns results
query.addCriteria(Criteria.where("onlineDate").lte(date))
is this something that I am not allowed to compare with null date.
You basically need to include conditions to accept the null in the results. Right now you are asking "is greater than" a supplied date, and null is not "greater than", therefore it is exluded.
This means adding an $or condition to test for both possible conditions on that field:
Criteria criteria = Criteria.where("onlinedate").lte(date).orOperator(
Criteria.where("offlinedate").gte(date),
Criteria.where("offlinedate").is(null)
);
Query query = new Query().addCriteria(criteria);
System.out.println(query.getQueryObject());
Which would give you:
{
"onlinedate" : { "$lte" : date } ,
"$or" : [
{ "offlinedate" : { "$gte" : date } } ,
{ "offlinedate" : null }
]
}
That is the correct translation of altering your query to allow the null value, but it does make me think that your basic thinking on the query is incorrect since this would return results that basically say "still online". That may be what you want, but then again you might have been intending to ask something different.
If in fact you were looking for "all" document that did not have the supplied date value "betweeen" the two values in the document, then you would instead "invert" the range expressions to match documents "between" and then "invert" the result with $nor instead:
Criteria criteria = new Criteria().norOperator(
Criteria.where("onlinedate").gte(date)
.and("offlinedate").lte(date)
);
Query query = new Query().addCriteria(criteria);
System.out.println(query.getQueryObject());
Which results in a query like this:
{
"$nor" : [
{
"onlinedate" : { "$gte" : date },
"offlinedate" : { "$lte" : date }
}
]
}
Which is returning results both "before" and "after" the range between the two date properties, or where the "offline" date was null and the range is not "closed".
It depends on which set of results you actually want, being either that you include the null values to find the data that is "still open" or you just "exclude the range" to find the data that falls outside of the range instead.

How to get just the desired field from an array of sub documents in Mongodb using Java

I have just started using Mongo Db . Below is my data structure .
It has an array of skillID's , each of which have an array of activeCampaigns and each activeCampaign has an array of callsByTimeZone.
What I am looking for in SQL terms is :
Select activeCampaigns.callsByTimeZone.label,
activeCampaigns.callsByTimeZone.loaded
from X
where skillID=50296 and activeCampaigns.campaign_id= 11371940
and activeCampaigns.callsByTimeZone='PT'
The output what I am expecting is to get
{"label":"PT", "loaded":1 }
The Command I used is
db.cd.find({ "skillID" : 50296 , "activeCampaigns.campaignId" : 11371940,
"activeCampaigns.callsByTimeZone.label" :"PT" },
{ "activeCampaigns.callsByTimeZone.label" : 1 ,
"activeCampaigns.callsByTimeZone.loaded" : 1 ,"_id" : 0})
The output what I am getting is everything under activeCampaigns.callsByTimeZone while I am expecting just for PT
DataStructure :
{
"skillID":50296,
"clientID":7419,
"voiceID":1,
"otherResults":7,
"activeCampaigns":
[{
"campaignId":11371940,
"campaignFileName":"Aaron.name.121.csv",
"loaded":259,
"callsByTimeZone":
[{
"label":"CT",
"loaded":6
},
{
"label":"ET",
"loaded":241
},
{
"label":"PT",
"loaded":1
}]
}]
}
I tried the same in Java.
QueryBuilder query = QueryBuilder.start().and("skillID").is(50296)
.and("activeCampaigns.campaignId").is(11371940)
.and("activeCampaigns.callsByTimeZone.label").is("PT");
BasicDBObject fields = new BasicDBObject("activeCampaigns.callsByTimeZone.label",1)
.append("activeCampaigns.callsByTimeZone.loaded",1).append("_id", 0);
DBCursor cursor = coll.find(query.get(), fields);
String campaignJson = null;
while(cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
campaignJson = campaignDBO.toString();
System.out.println(campaignJson);
}
the value obtained is everything under callsByTimeZone array. I am currently parsing the JSON obtained and getting only PT values . Is there a way to just query the PT fields inside activeCampaigns.callsByTimeZone .
Thanks in advance .Sorry if this question has already been raised in the forum, I have searched a lot and failed to find a proper solution.
Thanks in advance.
There are several ways of doing it, but you should not be using String manipulation (i.e. indexOf), the performance could be horrible.
The results in the cursor are nested Maps, representing the document in the database - a Map is a good Java-representation of key-value pairs. So you can navigate to the place you need in the document, instead of having to parse it as a String. I've tested the following and it works on your test data, but you might need to tweak it if your data is not all exactly like the example:
while (cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
List callsByTimezone = (List) ((DBObject) ((List) campaignDBO.get("activeCampaigns")).get(0)).get("callsByTimeZone");
DBObject valuesThatIWant;
for (Object o : callsByTimezone) {
DBObject call = (DBObject) o;
if (call.get("label").equals("PT")) {
valuesThatIWant = call;
}
}
}
Depending upon your data, you might want to add protection against null values as well.
The thing you were looking for ({"label":"PT", "loaded":1 }) is in the variable valueThatIWant. Note that this, too, is a DBObject, i.e. a Map, so if you want to see what's inside it you need to use get:
valuesThatIWant.get("label"); // will return "PT"
valuesThatIWant.get("loaded"); // will return 1
Because DBObject is effectively a Map of String to Object (i.e. Map<String, Object>) you need to cast the values that come out of it (hence the ugliness in the first bit of code in my answer) - with numbers, it will depend on how the data was loaded into the database, it might come out as an int or as a double:
String theValueOfLabel = (String) valuesThatIWant.get("label"); // will return "PT"
double theValueOfLoaded = (Double) valuesThatIWant.get("loaded"); // will return 1.0
I'd also like to point out the following from my answer:
((List) campaignDBO.get("activeCampaigns")).get(0)
This assumes that "activeCampaigns" is a) a list and in this case b) only has one entry (I'm doing get(0)).
You will also have noticed that the fields values you've set are almost entirely being ignored, and the result is most of the document, not just the fields you asked for. I'm pretty sure you can only define the top-level fields you want the query to return, so your code:
BasicDBObject fields = new BasicDBObject("activeCampaigns.callsByTimeZone.label",1)
.append("activeCampaigns.callsByTimeZone.loaded",1)
.append("_id", 0);
is actually exactly the same as:
BasicDBObject fields = new BasicDBObject("activeCampaigns", 1).append("_id", 0);
I think some of the points that will help you to work with Java & MongoDB are:
When you query the database, it will return you the whole document of
the thing that matches your query, i.e. everything from "skillID"
downwards. If you want to select the fields to return, I think those will only be top-level fields. See the documentation for more detail.
To navigate the results, you need to know that a DBObjects are returned, and that these are effectively a Map<String,
Object> in Java - you can use get to navigate to the correct node,
but you will need to cast the values into the correct shape.
Replacing while loop from your Java code with below seems to give "PT" as output.
`while(cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
campaignJson = campaignDBO.get("activeCampaigns").toString();
int labelInt = campaignJson.indexOf("PT", -1);
String label = campaignJson.substring(labelInt, labelInt+2);
System.out.println(label);
}`

Parsing strings to mongodb query documents with operators in java

In a project I'm working on, at one point I read a query to mongodb from a string. I've been using com.mongodb.util.JSON.parse(querystring) to read the query, which worked fine until I started reading queries that contained operators like $max and $min. At that point, rather than using mongodb's $max operator, the parser instead creates a "$max" field. For instance,
the input string:
{ $query : { state : "AL" } , $max : { pop : 9058 } }
is parsed to the DBObject:
{ "$query" : { "state" : "AL"} , "$max" : { "pop" : 9058}}
When I then look for a DBCursor with that query document, I get a cursor of size 0 (no matching document found in the databse), presumably because there are no documents with "$query" or "$max" fields.
Is there something I can use besides JSON.parse()? I'm not averse to writing my own function for it, but how can I get a DBObject that recognizes the $ operators as operators and not fields?
Any advice would be appreciated!
The following code snippet using query modification operator $max seems to work fine.
/* {$query:{state:"AL"}, "$max":{pop:10000}}*/
String s = "{$query:{state:\"AL\"}, \"$max\":{pop:10000}}";
DBObject dbObject = (DBObject) JSON.parse(s);
System.out.println("\nFind all: ");
DBCursor cursor = collection.find(dbObject);
try {
while (cursor.hasNext()) {
DBObject cur = cursor.next();
System.out.println(cur);
}
} finally {
cursor.close();
}
Make sure you have specified index on pop.
db.zips.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "test.zips",
"name" : "id"
},
{
"v" : 1,
"key" : {
"pop" : 1
},
"ns" : "test.zips",
"name" : "pop_1"
}
]
See the following link for detail.
http://docs.mongodb.org/manual/reference/operator/max/
Just in case you are interested in using aggregation operators $max or $min, the following link provide details and sample code.
http://docs.mongodb.org/ecosystem/tutorial/use-aggregation-framework-with-java-driver/
So it turns out the DBObject as given up there worked out fine. It returns a cursor with a size of 0, true, but the DBCursor's length is actually the thing I was looking for. (Previously, I had been checking whether the cursor's size was 0, and if it was, returning null.)
I'm not quite sure what the difference between size and length is in a DBCursor (the difference between size and count is apparent, but I'm not sure what length is supposed to be), but it works now. In the case above, size and count were both 0 but length was the desired number.

JSONObject vs JARRAY getting array length

I have a JSONObject that I pass to a method to get processed into ArrayLists.
{
"set0" :
{
"title" : "Do you win?",
"answer" : "1",
"imgUrl" : "imag",
"id" : "1"
},
"set1" :
{
"title" : "Did you loose?",
"answer" : "1",
"imgUrl" : "imag",
"id" : "2"
}
}
The method is structured like this:
questions(JSONObject result){
// initialization
for(int i=0;i < result.length();i++){
question_list.add(result.getJSONObject("set"+i).getString("title"));
answer_list.add(result.getJSONObject("set"+i).getString("answer"));
img_list.add(result.getJSONObject("set"+i).getString("imgUrl"));
question_batch.add(result.getJSONObject("set"+i).getInt("id"));
}
// rest of code
}
I normally see the jsonarray being used for the loop condition and not the jsonobject itself, when my jsonobject gets bigger but in the same structure as posted above will result.length() start to yield a wrong result and is this an incorrect implementation for someone who will want to iterate through their jsonobject?
I'm not sure if your implementation is "incorrect", but this is how I generally iterate through JSON.
int i = 0;
boolean continue = true;
while (continue){
if (result.has("set" + i){
JSONObject set = result.get("set" + i.toString());
if (set.has("title"){
question_list.add(set.getString("title");
}
if (set.has("answer"){
answer_list.add(set.getString("answer");
}
if (set.has("imgUrl"){
image_list.add(set.getString("imgUrl");
}
if (set.has("id"){
question_batch.add(set.getInt("id");
}
i++;
}else{
continue = false;
}
}
This is a little more efficient because you don't have to create a new JSON object everytime you want to pull data out.
Also I would make a new class to handle the JSON objects, and make the array list of that type, so you only have to add one object for each iteration. This way, you can be sure that your questions/answers/imageURLs/id's are all kept together. Like this:
JSONObject set = result.get("set" + i.toString());
QuestionObject newQuestion = new QuestionObject();
if (set.has("title"){
newQuestion.setTitle(result.getString("title");
}
if (set.has("answer"){
newQuestion.setAnswer(result.getString("answer");
}
if (set.has("imgUrl"){
newQuestion.setImageURL(result.getString("imgUrl");
}
if (set.has("id"){
newQuestion.setID(result.getInt("id");
}
questionList.add(newQuestion);
Also, in case you didn't notice, you can directly parse integers using json.getInt() for your "id" field instead of string.
You might want to have a look to Jackson also. That is also quite straightforward, and your code might benefit from using it.

parsing AggregationOutput mongo java driver

I have an aggregate that returns 3 results
{ "serverUsed" : "/127.0.0.1:27017" , "result" : [ { "_id" : "luke" , "times" : 56} , { "_id" : "albert" , "times" : 28} , { "_id" : "matt" , "times" : 28}] , "ok" : 1.0}
however when I try to iterate over the results, the code enter an endless loop (can't understand why!!)
AggregationOutput output = coll.aggregate( match1, unwind, match2, group, sort, limit);
Iterable<DBObject> list= output.results();
while(list.iterator().hasNext()){
String id = (String) list.iterator().next().get("_id");
int times = Integer.parseInt(list.iterator().next().get("times").toString());
System.out.println("ID IS "+id+" time: "+times);
}
Also the output repeats the first result:
ID IS luke time: 56
ID IS luke time: 56
ID IS luke time: 56
ID IS luke time: 56
ID IS luke time: 56
...
I really don;t understand why this iteration is not working. Please help!
It seems that you are using new iterator every time you access a field in DBObject, ie you're using list.iterator() several times within the loop. list.iterator().next() returns you the first element in the collection. So you end up accessing the first DBObject.
Try this:
Iterable<DBObject> list= output.results();
while(list.iterator().hasNext()){
DBObject obj = list.iterator().next();
String id = obj.get("_id");
int times = Integer.parseInt(obj.get("times").toString());
System.out.println("ID IS "+id+" time: "+times);
}
Or perhaps use a foreach loop:
for (DBObject obj : output.results()) {
String id = obj.get("_id");
int times = Integer.parseInt(obj.get("times").toString());
System.out.println("ID IS "+id+" time: "+times);
}
I know, this is an old thread, and it's already been answered, but the answers are optimizations. The actual reason why you are getting the repeated answers is that each time you request .iterator(), it returns back an iterator object that starts from the top of the list.
So the while loop condition of:
list.iterator().hasNext()
will always return true. Correspondingly, the subsequent
list.iterator().next()
will always return back the first element.
What should be done is this:
AggregationOutput output = coll.aggregate( match1, unwind, match2, group, sort, limit);
Iterable<DBObject> list= output.results();
Iterator<DBObject> iterator= list.iterator();
while(iterator.hasNext()){
DBObject obj = iterator.next();
String id = (String) obj.get("_id");
int times = Integer.parseInt(obj.get("times").toString());
System.out.println("ID IS "+id+" time: "+times);
}
But technically, the for loop mentioned by #Aqua is a lot cleaner to use. This answer was just to clarify the why, not the how.
One possible flaw in your code is you are calling Iterator.next() method multiple times. Store its value in a variable and use variable instead of calling multiple times.
Eg:
DBObject obj = list.iterator().next();
String id = (String) obj.get("_id");
int times = Integer.parseInt(obj.get("times").toString());`

Categories