I am trying to create object dynamically from csv file. I have get to this part:
Assuming I have this class:
package com.example
public TestClass {
public int hi = 0;
public String ho = "ho";
}
I also have this csv:
hi,ho
1,"hello"
In the codes below, classIdentifier is a string of the class name (i.e. "com.example.TestClass"), and maps is a HashMap that stores the data read from the csv file.
Class<T> c = (Class<T>) classIdentifier.getClass();
Set<String> keys = maps.keySet();
for(String key : keys) {
System.out.println(classIdentifier, c.getDeclaredField(key).getType());
System.out.println(maps.get(key));
c.getDeclaredField(key).set(classIdentifier, c.getDeclaredField(key).getType().cast(maps.get(key)));
}
The above code will printout expected values:
int
1
However this will throw the following error in the cast part:
java.lang.ClassCastException: Cannot cast java.lang.String to int
Although in the given example, hi is int and ho is string, I do want to extend the usage of this code to be able to convert with any type. That means I have no prior knowledge of the fields' type until I use the c.getDeclaredField(key).getType() (That's what I mean by dynamic).
I am just wondering how can I fix it?
Related
I have something like:
Map<String, Object> hashMap;
When I do something like:
hashMap.get("binary"), I get value as: {size=5642, name=abc}
Here key is binary and value is an Object of Type Object and is {size=5642, name=abc}
Note the values dont belong to a particular class.
In Python I can do something like hashMap["binary"]["size"], was wondering what would be the equivalent in java
How do I get the value of size directly without parsing the above string?
The value is not of Type Object, but of some type that extends from Object (in java everything extends Object implicitly). Let's call it "X"
Now, it doesn't work like python because unlike python java doesn't have that dynamic nature.
{size=5642, name=abc} is probably a string representation of that type X. This is what you see in a debugger or maybe when trying to print the value on console with System.out.println or something.
Now first of all figure out which type is it:
Object value = hashMap.get("binary")
System.out.println(value.getClass().getName());
It will print the class name
Then check the source of that class, probably it looks like this:
public class X {
private final int size;
private final String name;
... // constructor, other stuff maybe
// these are called "getters" in java world
public int getSize() {return size;}
public String getName() {return name;}
}
From that point you have 2 ways to get the size:
Object value = hashMap.get("binary");
int size = ((X)value).getSize(); // This is called downcasting
The drawback of this method is that you don't utilize the power of generics
So the better option is a refactoring if its possible of course:
Map<String, X> hashMap = ...
X value = hashMap.get("binary");
value.getSize();
One final note:
If it happens that the value is of type String, you won't be able to get the size other than parsing the value with regular expression or something. In this case consider a refactoring as a better option.
i have arraylist:
protected List<Opiskelija> opiskelijat = new ArrayList<>();
And i woould like to add String type to it. i get error such as:
error: incompatible types: Opiskelija cannot be converted to String
for (String alkio : opiskelijat) {
ive tried to research but have not found solution
add a toString() method to Opiskelija.java and call it while iterating:
for (Opiskelija opiskelija: opiskelijat) {
String asd = opiskelija.toString();
}
List<Opiskelija> opiskelijat can only hold objects that are of type Opiskelija. You can't add String type objects to it.
for (String alkio : opiskelijat) { } iterates over each item in opiskelijat, handling each one as a String. It fails to do so because of the aforementioned and that's why you get the error.
You can iterate through opiskelijat by for (Opiskelija alkio : opiskelijat) { } which will let you do stuff with each Opiskelija object one at a time. Every object in Java also has toString() method as they extend the base type Object that has it. You can call it on Opiskelija too (as in opiskelija.toString()) but it's likely not what you are looking for unless you have overridden the method in your class Opiskelija.
A bit more clarification is needed to give a helpful answer. What are you trying to achieve exactly?
Is there any way to declare the various types in the tuple dynamically?
I found a way to declare the number of columns in the Tuple dynamically:
env.readCsvFile(filePath).tupleType(Tuple.getTupleClass(3))
But without any type parameters, it throws as error:
Exception in thread "main" org.apache.flink.api.common.functions.InvalidTypesException: Tuple needs to be parameterized by using generics.
I wanted to use all the elements in the Tuple as simple String. The following works:
env.readCsvFile(filePath).types(String.class, String.class);
This results in a Tuple2(String,String) type. But in my case, I don't know how many columns of data there is in the csv. But I'm fine reading all the columns as Strings. (I understand that there's limit of max 25 columns)
I even tried reading by specifying the sub-type of CsvInputFormat:
env.readFile(new TupleCsvInputFormat(filePath,TypeInformation.of(String.class), filePath);
But couldn't get it to compile. Wasn't sure how to use this for my case. I was also unsure on how to extend the Tuple class to achieve the same (if possible). TypeHint seems to require me to know the number of columns before-hand.
I'm not sure about the other env.read...() methods. I tried a few, but a few methods like ignoreFirstLine() were not available. They only come with the CsvReader.
So, can someone kindly help me figure out the best approach to read a csv if the number of columns can be arbitrary (passed by input), and to read each element of the Tuple as a simple String?
It possible to write your own method to read CSV files. Maybe something like this:
public static void main(String[] args) throws Exception {
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
int n = 3; // number of columns here
Class[] types = IntStream.range(0, n).mapToObj(i -> String.class).toArray(Class[]::new);
DataSet<Tuple> csv = readCsv(env, "filename.csv", types);
csv.print();
}
private static DataSource<Tuple> readCsv(ExecutionEnvironment env, String filename, Class[] fieldTypes) {
TupleTypeInfo<Tuple> typeInfo = TupleTypeInfo.getBasicAndBasicValueTupleTypeInfo(fieldTypes);
TupleCsvInputFormat<Tuple> inputFormat = new TupleCsvInputFormat<>(new Path(filename), typeInfo);
return new DataSource<>(env, inputFormat, typeInfo, Utils.getCallLocationName());
}
Note: this method skips calling configureInputFormat method in the CsvReader class. And if you need it you can do it.
I am implementing a Utility method that accept String as parameter and convert to object with generic. I can achieve with the code below but the weak point is every object that need to parse must have a string constructor. There is no way of defining that the object must have String constructor. Is there any other better way to achieve this by Using polymorphism or generic?
AAA.java
public class AAA {
private String id;
private String description;
public AAA(String str) {
// Do parsing...
}
Utility method.
public static <T extends Base> List<T> readFile(File file, Class<T> type) {
List<T> collection = new ArrayList<T>();
// Read file line by line and convert to Instance
Constructor<T> ctor = type.getConstructor(String.class);
T newInstance = ctor.newInstance(line);
if (newInstance != null) {
collection.add(newInstance);
}
return collection;
}
Usage:
List<AAA> list = FileUtil.readFile(file, AAA.class);
I assume that your POJO classes (the one which actually contain the data) are usually in the format you have in your example. Meaning all the fields are String values. If this is not the case my solution needs a little bit of refinement.
My suggestion is to use reflection for the use case you describe. I have been very successful with this in the past. Although reflection can pose severe performance penalties if applied badly.
Parsing code could look roughly like the following. I omit a method header since the one you provide already looks good. The code I provide will assume that in the String[] variable line we find an already parsed line of your CSV file. Each element of the array containing one column of the CSV line.
String[] line; // initialised as explained above
T newInstance = T.newInstance(); // invoke default constructor
Field[] fields = newInstance.getClass().getDeclaredFields(); // use reflection to read all fields
int count = 0;
for(Fields f : fields) {
f.set(newInstance, line[count]);
count++;
}
Disclaimer: The above code does not do any boundary checks! The assumption is that the CSV lines and the number of fields in a class have the same length!
On the Field objects what I usually do is to also call getAnnotation to check whether or not a certain annotation is set on the field. This allows you to write classes like that:
public class AAAAnnotated {
#MyCSVAnnotation
String field1;
#MyCSVAnnotation
String field2;
String field3;
}
If your code checks whether or not fields are annotated with your annotations you can even control in the POJO classes which fields to load form CSV and which to leave untouched.
I'm trying to return all of the names that have been provided to a JSONObject class (as in, all of the keys in their own array, without their associated values). My code is currently as follows:
String names[] = new String[10];
names = JSONObject.getNames(jsonObj);
The method I'm trying to get the array from is this:
public static String[] getNames(JSONObject jo) {
int length = jo.length();
if (length == 0) {
return null;
}
Iterator iterator = jo.keys();
String[] names = new String[length];
int i = 0;
while (iterator.hasNext()) {
names[i] = (String)iterator.next();
i += 1;
}
return names;
}
And this is the error I'm getting:
The method getNames(JSONObject) is undefined for the type JSONObject
However, if I set my code to deliberately return and assign it to a variable of the wrong type:
int inames = JSONObject.getNames(jsonObj);
Highlighting either the JSONObject part or the jsonObj parameter of that above line cause Eclipse to display the following error:
Type mismatch: cannot convert from String[] to int
Whereas highlighting the getNames method still provides the same error as before.
What am I to garner from this? Eclipse seems to know that the method is there, since it knows its correct return type, yet it's claiming that the method is undefined in the class.
It seems that method getNames is not a static method of JSONObject. Am I right?
Inside which class is it declared?
If it's class X you should do:
X.getNames(jsonObj);
Are you sure you don't have 2 jar-files on your classpath containing a class named JSONObject?
The classes that I was making use of were in a separate package when I was having my problem. Moving all of JSON classes into same package as the classes I wrote and removing the package import
import org.json.*;
resolved it.