Mongodb has an update function, where it can increment pre-existing fields. However, I found that it could only update flat JSON. Whenever there's a JSONObject inside of a JSONObject, with a value I want to increment, I can't actually seem to do it. It will return this error:
com.mongodb.WriteConcernException: Write failed with error code 14 and error message
'Cannot increment with non-numeric argument: {laneQty: { BOTTOM: 1 }}'
As you can see, I tried update incrementing laneQty.BOTTOM by 1. I don't want to write an algorithm to change every single layered json field into dot notation(like laneQty.BOTTOM), so is there a way to either turn the JSON into dot notation pre-upsert?
For now my general upsert function looks like this:
public boolean incrementJson(BasicDBObject json, String colName, ArrayList<String> queryParams, ArrayList<String> removeParams){
/*make sure the game id AND the main player id can't both be the same.
If either/or, it's fine. We don't want duplicates.
*/
BasicDBObject query = new BasicDBObject();
DBCollection collection = db.getCollection(colName);
for(int i = 0; i < queryParams.size(); i++){
String param = queryParams.get(i);
query.put(param, json.get(param));
}
for(String param : removeParams){
json.remove(param);
}
return collection.update(query, new BasicDBObject("$inc", json), true, false).isUpdateOfExisting();
}
Is there any suggested upgrades to this code that could make it easily update layered json as well? Thank you!
By the way, it'll be very hard for me to hardcode this. There are a ton of layered objects and that would take me forever. Also, I am not in complete control of which fields are populated in the layers, so I can't just say laneQty.BOTTOM every single time because it will not always exist. Prior to upserting, the BasicDBObject json was actually a java bean parsed into BasicDBObject. This is its constructor if it's of any help:
public ChampionBean(int rank, int division, int assists, int deaths, int kills, int qty, int championId,
HashMap<String, Integer> laneQty, HashMap<String, Integer> roleQty,
ParticipantTimelineDataBean assistedLaneDeathsPerMinDeltas,
ParticipantTimelineDataBean assistedLaneKillsPerMinDeltas, ParticipantTimelineDataBean creepsPerMinDeltas,
ParticipantTimelineDataBean csDiffPerMinDeltas, ParticipantTimelineDataBean damageTakenDiffPerMinDeltas,
ParticipantTimelineDataBean damageTakenPerMinDeltas, ParticipantTimelineDataBean goldPerMinDeltas,
ParticipantTimelineDataBean xpDiffPerMinDeltas, ParticipantTimelineDataBean xpPerMinDeltas, int wins,
int weekDate, int yearDate) {
super();
this.rank = rank;
this.division = division;
this.assists = assists;
this.deaths = deaths;
this.kills = kills;
this.qty = qty;
this.championId = championId;
this.laneQty = laneQty;
this.roleQty = roleQty;
this.assistedLaneDeathsPerMinDeltas = assistedLaneDeathsPerMinDeltas;
this.assistedLaneKillsPerMinDeltas = assistedLaneKillsPerMinDeltas;
this.creepsPerMinDeltas = creepsPerMinDeltas;
this.csDiffPerMinDeltas = csDiffPerMinDeltas;
this.damageTakenDiffPerMinDeltas = damageTakenDiffPerMinDeltas;
this.damageTakenPerMinDeltas = damageTakenPerMinDeltas;
this.goldPerMinDeltas = goldPerMinDeltas;
this.xpDiffPerMinDeltas = xpDiffPerMinDeltas;
this.xpPerMinDeltas = xpPerMinDeltas;
this.wins = wins;
this.weekDate = weekDate;
this.yearDate = yearDate;
}
The participantTimelineDataBean is another bean with 4 int fields inside of it. I want to increment those fields (so yes it's only 2 layers deep, so if there's a solution with 2 layers deep availability I'll take that too).
Use the dot-notation:
new BasicDBObject("$inc", new BasicDBObject("laneQty.BOTTOM", 1) )
Alternative quick&dirty solution: Just collection.save the whole document under the same _id.
Use this library:
https://github.com/rhalff/dot-object
For example if you have an object like this:
var jsonObject = {
info : {
firstName : 'aamir',
lastName : 'ryu'
email : 'aamiryu#gmail.com'
},
}
then your node.js code would be like this:
var dot = require('dot-object');
var jsonObject = // as above ;-);
var convertJsonObjectToDot = dot.dot(jsonObject);
console.log(convertJsonObjectToDot);
Output will be as shown below:
{
info.firstName : 'aamir',
info.lastName : 'ryu',
info.email : 'aamiryu#gmail.com
}
Please bear with me, this is my first answer on stackoverflow ever, since i was searching for the same thing and i found one solution to it, hope it helps you out.
Related
I am trying insert an item in MongoDB using Java MongoDB driver.Before inserting I am trying to get nextId to insert,but not sure why I am always getting nextId as 4 .I am using below given method to get nextId before inserting any item in Mongo.
private Long getNextIdValue(DBCollection dbCollection) {
Long nextSequenceNumber = 1L;
DBObject query = new BasicDBObject();
query.put("id", -1);
DBCursor cursor = dbCollection.find().sort(query).limit(1);
while (cursor.hasNext()) {
DBObject itemDBObj = cursor.next();
nextSequenceNumber = new Long(itemDBObj.get("id").toString()) + 1;
}
return nextSequenceNumber;
}
I have total 13 record in my mongodb collection.What I am doing wrong here?
Please don't do that. You don't need create a bad management id situation as the driver already do this in the best way, just use the right type and annotation for the field:
#Id
#ObjectId
private String id;
Then write a generic method to insert all entites:
public T create(T entity) throws MongoException, IOException {
WriteResult<? extends Object, String> result = jacksonDB.insert(entity);
return (T) result.getSavedObject();
}
This will create a time-based indexed hash for id's which is pretty much more powerful than get the "next id".
https://www.tutorialspoint.com/mongodb/mongodb_objectid.htm
How can you perform Arithmetic operations like +1 to String
nextSequenceNumber = new Long(itemDBObj.get("id").toString()) + 1;
Try to create a Sequence collection like this.
{"id":"MySequence","sequence":1}
Then use Update to increment the id
// Query for sequence collection
Query query = new Query(new Criteria().where("id").is("MySequence"));
//Increment the sequence by 1
Update update = new Update();
update.inc("sequence", 1);
FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
findAndModifyOptions.returnNew(true);
SequenceCollection sequenceCollection = mongoOperations.findAndModify(query, update,findAndModifyOptions, SequenceCollection.class);
return sequenceModel.getSequence();
I found the work around using b.collection.count().I simply find the total count and incremented by 1 to assign id to my object.
I am a beginner at Java, and I'm having trouble understanding why I'm getting an error. I have a .csv file containing cities, provinces, and respective populations of Canada. I have been trying to read the file and then put the PROVINCE and POPULATION values into a HashMap (cana) via a key/value pair. I've created a HashSet (canada) to split up the .csv, and I would like to keep that as-is if possible.
My question is about the cana.add(provSet, pop1). I am getting an "cannot find symbol - method add(java.util.Set) error around the "put", and I can't figure out why. Can someone please help me understand what I've done wrong? Since I am a beginner, additional explanation would be greatly appreciated!
String filename = "canada.csv";
try
{
BufferedReader br = new BufferedReader(new FileReader("canada.csv"));
String line = null;
HashSet<String> canada = new HashSet<String>();
HashMap<Set<String>, Set<Integer>> cana = new HashMap<Set<String>, Set<Integer>>();
while((line=br.readLine())!=null) {
String city = line.split(",")[0];
canada.add(city);
String province = line.split(",")[1];
canada.add(province);
Set<String> provSet = new HashSet<String>(Arrays.asList(province));
String population = line.split(",")[2];
canada.add(population);
int p = new Integer(population);
Set<Integer> pop1 = new HashSet<Integer>(Arrays.asList(p));
cana.add(provSet, pop1); //ERROR
//Trying to find the most populated province
String maxProvince = "";
int maxProvPop = 0;
for(String province : cana.keySet()) {
int provPop = cana.get(province);
System.out.println(population);
if( provPop > maxProvPop )
{
maxProvPop = provPop;
maxProvince = province;
}
System.out.println("The most populated province is " + maxProvince + " with a population of " + maxProvPop);
}
I think you're mixing up the methods for HashSet and HashMap. You use the add method for HashSet, and put method for HashMap.
HashSet Documentation
HashMap Documentation
I would like to specify. May I receive elements only from DynamoDBIndexHashKey, not use DynamoDBHashKey?
I have a table with fields
#DynamoDBIndexHashKey (attributeName = "count", globalSecondaryIndexName = "count-index")
#DynamoDBHashKey(attributeName="cluster_output_Id)"
#DynamoDBRangeKey(attributeName="last_fetch)"
I have no #DynamoDBIndexRangeKey
It's code:
MyEntity myEntity = new MyEntity();
myEntity.setCount(1); // Integer
DynamoDBQueryExpression<NewsDynamoDb> queryExpression = new DynamoDBQueryExpression<NewsDynamoDb>()
.withHashKeyValues(myEntity)
.withIndexName("count-index");
queryExpression.setConsistentRead(false);
List<MyEntity> myCollection = mapper.query(MyEntity.class, queryExpression);
Error:
AmazonServiceException: Status Code: 400, AWS Service: AmazonDynamoDBv2, AWS Request ID: I97S04LDGO6FSF56OCJ8S3K167VV4KQNSO5AEMVJF66Q9ASUAAJG, AWS Error Code: ValidationException, AWS Error Message: One or more parameter values were invalid: Invalid number of argument(s) for the EQ ComparisonOperator
How I can get items from DynamoDBIndexHashKey?
P.s. Scan - work but not interesting to me, because in a further I want a sorting
Query with DynamoDBHashKey work. I have problems with DynamoDBIndexHashKey
same example
It is the answer to my question
entity:
#DynamoDBHashKey(attributeName="cluster_output_Id")
public Integer getCluster_output_Id() {
return cluster_output_Id;
}
#DynamoDBIndexHashKey(attributeName = "count", globalSecondaryIndexName = "count-index")
public Integer getCount() {
return count;
}
#DynamoDBRangeKey(attributeName="last_fetch")
#DynamoDBIndexRangeKey(attributeName = "last_fetch", globalSecondaryIndexName = "count-index")
public Date getLast_fetch() {
return last_fetch;
}
code:
dynamoDBMapper = new DynamoDBMapper(amazonDynamoDBClient);
MyClass myClass= new MyClass();
DynamoDBQueryExpression<MyClass > queryExpression = new DynamoDBQueryExpression<MyClass >();
myClass.setCount(1);
queryExpression.setHashKeyValues(myClass);
queryExpression.withIndexName("count-index"); // it's not necessarily
Condition rangeKeyCondition = new Condition();
rangeKeyCondition.withComparisonOperator(ComparisonOperator.NE)
.withAttributeValueList(new AttributeValue().withS(""));
queryExpression.setConsistentRead(false);
List entities = dynamoDBMapper.query(MyClass.class, queryExpression);
Thank you!
like explained here
Table table = dynamoDB.getTable("tableName");
Index index = table.getIndex("count-index");
ItemCollection<QueryOutcome> items = null;
QuerySpec querySpec = new QuerySpec();
querySpec.withKeyConditionExpression("count= :v_count > 0 ")
.withValueMap(new ValueMap() .withString(":v_count","1");
items = index.query(querySpec);
while (iterator.hasNext()) {
//.......
}
You cannot use Query to find items based on sort/range key only.
You can read more here.
In a Query operation, you use the KeyConditionExpression parameter to determine the items to be read from the table or index. You must specify the partition key name and value as an equality condition. You can optionally provide a second condition for the sort key (if present).
In this case your options are:
Scan operation with last_fetch as filter.
Redesign your database to have a GSI with last_fetch as partition key
Is it possible to parse a delimited file and find column datatypes? e.g
Delimited file:
Email,FirstName,DOB,Age,CreateDate
test#test1.com,Test User1,20/01/2001,24,23/02/2015 14:06:45
test#test2.com,Test User2,14/02/2001,24,23/02/2015 14:06:45
test#test3.com,Test User3,15/01/2001,24,23/02/2015 14:06:45
test#test4.com,Test User4,23/05/2001,24,23/02/2015 14:06:45
Output:
Email datatype: email
FirstName datatype: Text
DOB datatype: date
Age datatype: int
CreateDate datatype: Timestamp
The purpose of this is to read a delimited file and construct a table creation query on the fly and insert data into that table.
I tried using apache validator, I believe we need to parse the complete file in order to determine each column data type.
EDIT: The code that I've tried:
CSVReader csvReader = new CSVReader(new FileReader(fileName),',');
String[] row = null;
int[] colLength=(int[]) null;
int colCount = 0;
String[] colDataType = null;
String[] colHeaders = null;
String[] header = csvReader.readNext();
if (header != null) {
colCount = header.length;
}
colLength = new int[colCount];
colDataType = new String[colCount];
colHeaders = new String[colCount];
for (int i=0;i<colCount;i++){
colHeaders[i]=header[i];
}
int templength=0;
String tempType = null;
IntegerValidator intValidator = new IntegerValidator();
DateValidator dateValidator = new DateValidator();
TimeValidator timeValidator = new TimeValidator();
while((row = csvReader.readNext()) != null) {
for(int i=0;i<colCount;i++) {
templength = row[i].length();
colLength[i] = templength > colLength[i] ? templength : colLength[i];
if(colHeaders[i].equalsIgnoreCase("email")){
logger.info("Col "+i+" is Email");
} else if(intValidator.isValid(row[i])){
tempType="Integer";
logger.info("Col "+i+" is Integer");
} else if(timeValidator.isValid(row[i])){
tempType="Time";
logger.info("Col "+i+" is Time");
} else if(dateValidator.isValid(row[i])){
tempType="Date";
logger.info("Col "+i+" is Date");
} else {
tempType="Text";
logger.info("Col "+i+" is Text");
}
logger.info(row[i].length()+"");
}
Not sure if this is the best way of doing this, any pointers in the right direction would be of help
If you wish to write this yourself rather than use a third party library then probably the easiest mechanism is to define a regular expression for each data type and then check if all fields satisfy it. Here's some sample code to get you started (using Java 8).
public enum DataType {
DATETIME("dd/dd/dddd dd:dd:dd"),
DATE("dd/dd/dddd",
EMAIL("\\w+#\\w+"),
TEXT(".*");
private final Predicate<String> tester;
DateType(String regexp) {
tester = Pattern.compile(regexp).asPredicate();
}
public static Optional<DataType> getTypeOfField(String[] fieldValues) {
return Arrays.stream(values())
.filter(dt -> Arrays.stream(fieldValues).allMatch(dt.tester)
.findFirst();
}
}
Note that this relies on the order of the enum values (e.g. testing for datetime before date).
Yes it is possible and you do have to parse the entire file first. Have a set of rules for each data type. Iterate over every row in the column. Start of with every column having every data type and cancel of data types if a row in that column violates a rule of that data type. After iterating the column check what data type is left for the column. Eg. Lets say we have two data types integer and text... rules for integer... well it must only contain numbers 0-9 and may begin with '-'. Text can be anything.
Our column:
345
-1ab
123
The integer data type would be removed by the second row so it would be text. If row two was just -1 then you would be left with integer and text so it would be integer because text would never be removed as our rule says text can be anything... you dont have to check for text basically if you left with no other data type the answer is text. Hope this answers your question
I have slight similar kind of logic needed for my project. Searched lot but did not get right solution. For me i need to pass string object to the method that should return datatype of the obj. finally i found post from #sprinter, it looks similar to my logic but i need to pass string instead of string array.
Modified the code for my need and posted below.
public enum DataType {
DATE("dd/dd/dddd"),
EMAIL("#gmail"),
NUMBER("[0-9]+"),
STRING("^[A-Za-z0-9? ,_-]+$");
private final String regEx;
public String getRegEx() {
return regEx;
}
DataType(String regEx) {
this.regEx = regEx;
}
public static Optional<DataType> getTypeOfField(String str) {
return Arrays.stream(DataType.values())
.filter(dt -> {
return Pattern.compile(dt.getRegEx()).matcher(str).matches();
})
.findFirst();
}
}
For example:
Optional<DataType> dataType = getTypeOfField("Bharathiraja");
System.out.println(dataType);
System.out.println(dataType .get());
Output:
Optional[STRING]
STRING
Please note, regular exp pattern is vary based on requirements, so modify the pattern as per your need don't take as it is.
Happy Coding !
I have been using VFP 8.0 for quite sometime and one of the most thing I enjoyed about it is the macro function;
name = "Paul James"
age = 25
result = My name is &name, I am &age years old.
I could also do,
dimension x[5];
x[0] = "box"
x[1] = "area"
text.&x[0]..text = "textbox" ---> textbox.text="textbox"
text.&x[1]..text = "textarea" ---> textarea.text="textarea"
That's with the FoxPro thing, I seem to have grown attached to it and am somewhat inclined wishing such exist with OOs Languages like Java (or it really does, im just missing some extra research?), anyway, I wanted to have something like that here's my problem;
I have a JSON Array, which I get all names of the response and store it in a temporary array by using the "names()" method provided in the android code factory.
Purposely, I want to create an array for each name in the temporary array that was created from the method;
To illustrate;
JSONArray response =
[{"name":"a","middlename":"aa","surname":"aaa"},{"name":"b","middlename":"bb","surname":"bbb"},{"name":"c","middlename":"cc","surname":"ccc"}]
temp[] = [{name,middlename,surname}];
Desired Output:
String[] name = new String[response.firstobject.length];
String[] middlename = new String[response.firstobject.length];
String[] surname = new String[response.firstobject.length];
Here's my actual code; The JSON Parser
#SuppressWarnings("null")
public ArrayList<String> parseJson(JSONArray ja) throws JSONException{
ArrayList<String> listItems = new ArrayList<String>();
String[] temp = null;
//Get all the fields first
for (int i=0; i<=0; ++i){
JSONObject jo = ja.getJSONObject(i);
if(jo.length()>0){
temp = new String[jo.names().length()];
for(int x=0; x<jo.names().length(); ++x){
temp[x] = jo.names().getString(x);
}
}
}
}
So I'm kinda stuck in the desired output, is this possible in the first place? Why I'm doing this, is that because I wanted to create a generic JSON response method; So that I don't have to remember all the names of the response just to use them. Im looking for a java/android solution (most likely the one that works with android).
Thanks in Advance!
I wouldn't necessarily try to replicate what you can do in Visual FoxPro since it's usually a good idea in that language to avoid macro substitution unless you absolutely have to use it, and you can't use a name expression instead.
Here is an example of a name expression:
STORE 'city' TO cVarCity
REPLACE (cVarCity) WITH 'Paris'
This is much faster especially in loops.
On the Java side you're probably looking at using the Reflection API.
I also work with vfp and I have some routines. Perhaps these functions serve you STRTRAN, CHRTRAN:
//--------- ejemplos :
// STRTRAN("Hola * mundo","*", "//") ==> "Hola // mundo"
public String STRTRAN(String cExpression, String cFindString, String cReplacement){
return cExpression.replace(cFindString, cReplacement);
}
//------------------ ejemplos:
// miToolkit.CHRTRAN("ABCDEF", "ACE", "XYZ"); // muestra XBYDZF. ok
// miToolkit.CHRTRAN("ABCDEF", "ACE", "XYZQRST"); // muestra XBYDZF. ok
// miToolkit.CHRTRAN("ABCD", "ABC", "YZ"); // muestra YZCD. No es como fox
public String CHRTRAN(String cString, String cFindChars, String cNewChars){
String cResult = cString;
char[] aFindChars;
char[] aNewChars;
int nLength = cFindChars.length();
aFindChars = cFindChars.toCharArray();
aNewChars = cNewChars.toCharArray();
if(cNewChars.length() < nLength){
nLength = cNewChars.length() ;
}
for(int i=0; i < nLength; i++){
cResult = cResult.replace( aFindChars[i], aNewChars[i] );
}
return cResult;
}
Saludos,
César Gómez,
Lima-Perú