I have a ArrayList<Metadata> and i want to know if there is a Java API for working with CSV files which has a write method which accepts a ArrayList<> as parameter similar to LinqToCsv in .Net. As i know OpenCSV is available but the CsvWriter class doesn't accept a collection.
My Metadata Class is
public class Metadata{
private String page;
private String document;
private String loan;
private String type;
}
ArrayList<Metadata> record = new ArrayList<Metadata>();
once i populate the record, i want to write each row into a csv file.
Please suggest.
Surely there'll be a heap of APIs that will do this for you, but why not do it yourself for such a simple case? It will save you a dependency, which is a good thing for any project of any size.
Create a toCsvRow() method in Metadata that joins the strings separated by a comma.
public String toCsvRow() {
return Stream.of(page, document, loan, type)
.map(value -> value.replaceAll("\"", "\"\""))
.map(value -> Stream.of("\"", ",").anyMatch(value::contains) ? "\"" + value + "\"" : value)
.collect(Collectors.joining(","));
}
Collect the result of this method for every Metadata object separated by a new line.
String recordAsCsv = record.stream()
.map(Metadata::toCsvRow)
.collect(Collectors.joining(System.getProperty("line.separator")));
EDIT
Should you not be so fortunate as to have Java 8 and the Stream API at your disposal, this would be almost as simple using a traditional List.
public String toCsvRow() {
String csvRow = "";
for (String value : Arrays.asList(page, document, loan, type)) {
String processed = value;
if (value.contains("\"") || value.contains(",")) {
processed = "\"" + value.replaceAll("\"", "\"\"") + "\"";
}
csvRow += "," + processed;
}
return csvRow.substring(1);
}
By using CSVWriter, you could convert the ArrayList to an array, and pass that to the writer .
csvWriter.writeNext(record.toArray(new String[record.size()]));
If you have an ArrayList of Objects (Metadata in your case) you would use the BeanToCSV instead of the CSVWriter.
You can look at the BeanToCSVTest in the opencsv source code for examples of how to use it.
Related
I am trying to implement a csv reader into my class in Java using eclipse. I keep getting an error for the add method "add(Person) in the type list is not applicable for the arguments (String[]). What am I doing wrong?
public static List<Person> readPersons(String fileName)
throws FileNotFoundException {
int count = 0;
List<Person[]> content = new ArrayList<>();
try(BufferedReader cv = new BufferedReader(new FileReader(fileName))){
String line = "";
while ((line = cv.readLine()) != null) {
content.add(line.split(","));
}
} catch (FileNotFoundException e) {
}
return content;
}
Also, how do I implement this FileNotFoundException extender? It is required in the program.
The line.split( "," ) method will return an array of strings.
What it does is: The original string line is split into an array of strings. In that array, every string is a substring of line which is separated by a comma.
For example, if line is "Peter,Smith,38", the following array of strings will be returned: [ "Peter", "Smith", "38" ].
But, since your List only can contain objects of the type Person, it cannot take the String[] array returned by line.split( "," ).
So assuming you have an Constructor for Person that looks like this: Person( String firstName, String secondName, int age ) you would have to change your while loop to something like this:
while ( ( line = cv.readLine( ) ) != null )
{
// Get the data from the CSV object
String[] csvData = line.split( "," );
// Create a Person object with the retrieved data
Person personFromCsvLine = new Person( csvData[0], csvData[1], Integer.parseInt( csvData[2] );
// Now you can add the person object to your list
content.add( personFromCsvLine );
}
The answer by be-ta is correct.
I would like to point out that for reading a CSV you might want to consider using an existing solution in your code, like:
Apache Commons CSV
Jackson CsvMapper
Example code
These libraries will help with quoted / unquoted columns and conversion of values to the proper datatypes.
I have four strings I need to append.
String food = extras.getString("food");
String file = extras.getString("file");
String parcel = extras.getString("parcel");
String others = extras.getString("others");
String itemList = new StringBuilder(food).append("\n")
.append(file).append("\n")
.append(parcel).append("\n")
.append(others).toString();
This code will prints, if I choose Food and File.
Food
File
null
null
Since Parcel and Others have no values (which is null), how to make it will print like below?
Food
File
I tried to use if else but it will be too long (14 possibilities). Is there any other way to make it shorter and effective?
Java 8's streaming capabilities offer a pretty neat way of doing this:
String itemList =
Stream.of(food, file, parcel, others)
.filter(Objects::nonNull)
.collect(Collectors.joining("\n"));
EDIT:
For older versions of java, you could do something similar with a traditional for loop, although it would be clunkier:
StringBuilder sb = new StringBuilder();
for (String s : Arrays.asList(food, file, parcel, others)) {
if (s != null) {
sb.append(s).append('\n');
}
}
String itemList = sb.toString();
You can simply replace all null values
String food = "food";
String file = "file";
String parcel = null;
String others = null;
String itemList = new StringBuilder(food).append("\n").append(file).append("\n").append(parcel).append("\n").append(others).toString();
itemList=itemList.replaceAll("\n?null\n?", "");
System.out.println(itemList);
Output :
food
file
\n?null\n? \n? mean there can be one or no \n value on both side of null
so it will simply replace all values with empty string
If you want to go for below Java 8 then its possible through Ternary Operator in java. Please see below code snippet:
String itemList = new StringBuilder(food!=null?food+"\n":"")
.append(file!=null?file+"\n":"")
.append(parcel!=null?parcel+"\n":"")
.append(others!=null?others+"\n":"")
.toString();
The itemList will have the desired result.
Hope it helps.
I have an HashMap<Person,List<Pet> that needs to be saved into the textfile.
I wrote this method:
public void writeToNotepad(){
try(PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(textFileName + ".txt")))) {
if (map.size() < 1)
return;
Set<Map.Entry<Person, List<Pet>>> mapEntry = map.entrySet();
for (Map.Entry<Person, List<Pet>> mapEn :
mapEntry) {
Person p = mapEn.getKey();
Iterator<Pet> iter = mapEn.getValue().iterator();
while (iter.hasNext()){
Pet pet = iter.next();
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
}
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
but it saves map key multiple times(i think it's because of iterator)
here is output:
JOHN 10 MALE Dog 3
AISEC 12 MALE Loo Cat 12
AISEC 12 MALE Kitty Cat 4
As you see, Aisec repeats 2 times.
Sooner i need to read from this text file to fill map.
Is this good way to write to file, or i can use better?
...and what if the name contains two words with a blank in between??
Why do you need to invent your own format for serialization and de-serialization? why not use an industry standard like json, where you can utilize any of many libraries that can do the serialization and de-serialization. Do you need me to show you how?
EDIT:
OK, it turned out that using json is not as straightforward as I initially thought. Don't get me wrong, it is still better than custom format in the sense that it is bug-proof and supports edge cases like the one I described above.
The obstacle with json is that the key to every object and property has to be a String. So when the key is a user defined type (like Person in your case) it didn’t get serialized properly - there is a need for a transitional data structure before the serialization to json can be performed.
So this is what I did: for each entry in your dictionary, I create a map that holds two entries: a "person" entry with the json String representation of the Person object, and a "pets" entry with the json String representation of the list of pets. So the final String to be serialized is actually a List of Maps.
To give you an idea: every map entry looks like this as json:
{
"person":{"name":"AISEC","age":12,"sex":"MALE"},
"pets":[
{"name":"Loo","age":12,"type":"Cat"},
{"name":"Kitty","age":4,"type":"Cat"}
]
}
The deserialization is simply the reverse operation.
I am using Jackson library as json parser and writer
This is the serialization method. it returns a String that can be written to a file:
public String dictToJson(Map<Person, List<Pet>> map) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
// serialized dictionary is list of maps
List<Map<String, String>> jsonDictionary = new ArrayList<>();
for (Map.Entry<Person, List<Pet>> mapEntry : map.entrySet()) {
// each map entry becomes its own map instance with two entries
Map<String, String> jsonEntry = new HashMap<>();
// write person key as "person" with json string represetation of person object
jsonEntry.put("person", mapper.writeValueAsString(mapEntry.getKey()));
// write pets value as "pets" key with json string represetation of pets list
jsonEntry.put("pets", mapper.writeValueAsString(mapEntry.getValue()));
jsonDictionary.add(jsonEntry);
}
return mapper.writeValueAsString(jsonDictionary);
}
The de-serialization method accpets String (whole content of file):
public Map<Person, List<Pet>> jsonToDict(String json) throws IOException
{
Map<Person, List<Pet>> map = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
// read json String into list of maps
List<Map<String, String>> jsonDictionary =
mapper.readValue(json, new TypeReference<List<Map<String, Object>>>(){});
// each list item is a map with two entries
for (Map<String, String> jsonEntry : jsonDictionary) {
map.put(
mapper.readValue(jsonEntry.get("person"), Person.class),
mapper.readValue(jsonEntry.get("pets"), new TypeReference<List<Pet>>(){}));
}
return map;
}
usage and test method:
public static void main(String[] args)
{
Map<Person, List<Pet>> map ; {
map = new HashMap<>();
map.put(new Person("JOHN", 10, "MALE"),
Arrays.asList(new Pet[]{new Pet("Spot", 3, "Dog")}));
map.put(new Person("AISEC", 12, "MALE"),
Arrays.asList(new Pet[]{new Pet("Loo", 12, "Cat"), new Pet("Kitty", 4, "Cat")}));
}
try {
// serialize map into String
String serializedDict = dictToJson(map);
// write to file ...
System.out.println(serializedDict);
map.clear();
// de-serialize from String to map
map = jsonToDict(serializedDict);
// check if map was built ok
System.out.println(dictToJson(map));
} catch (Exception e) {
e.printStackTrace();
}
}
I overlooked the read+write part.
First some words about that thing: what you are actually looking for is a human readable way of serializing your data. There are various ways to get there: A) you define your own text format B) you use existing technologies, like JSON or XML.
My recommendation: learn how to use libraries that transform your data in JSON strings; then write those; and read them back using a JSON parser.
Further reading on that: writing and parsing
The key part is: no matter what you, you really have to think through what you are doing here. You have to define a reasonable format for your data; and then you have to write the code to create/parse that content.
But then, on the second part of your question; the current output that comes up:
Here; in your inner loop:
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
You are making a println call that prints all that information. Within your inner loop that loops on the entries of the inner list.
And you are really surprised that an inner loop that runs within an outer loop leads to this result?!
So, the answer is: you have to step back. You put code down that will print
Owner NAME AGE pet1 details
Owner NAME AGE pet2 details
and so on. You have to rework that code; and first you have to clarify the desired output format. For example, that could be:
Owner NAME AGE pet1 details | pet 2 details | and so forth
You could get there by creating a helper method like
public String toString(List<Pet> pets) {
Now you simply iterate your pet owners; and for each owner you print down the owner details; and then you use that method for the pets string.
Beyond that: you could look into overriding
public String toString()
on your Person and your Pet class. In other words: dont ask your objects for their details to build a string from that. Instead: tell the object to provide a string representation of itself.
And then doing a full printout would boil down to:
foreach mapEn
System.out.println("Owner: " + mapEn.getKey().toString() + " pets: " + mapEn.getValue().toString());
I am trying to parse a csv string like this
COL1,COL2,COL3
1,2,3
2,4,5
and map columns to a java object-
Class Person{
COL1,
COL2,
COL3;
}
Most of the libraries I found on google are for csv files but I am working with google app engine so can't write or read files. currently I am using split method but problems with this approach is
column that I am getting in csv string could vary as
COL1,COL3,COL2
don't want to use boiler plate code of splitting and getting each column.so what I need is list of column header and read all columns in a collection using header mapper. While iterating, map column value to a java object.
There are several question based on similar type of requirement but none of them helped me.
If anyone has done this before please could you share the idea? Thanks!
After searching and trying several libraries, I am able to solve it. I am sharing the code if anyone needs it later-
public class CSVParsing {
public void parseCSV() throws IOException {
List<Person> list = Lists.newArrayList();
String str = "COL1,COL2,COL3\n" +
"A,B,23\n" +
"S,H,20\n";
CsvSchema schema = CsvSchema.emptySchema().withHeader();
ObjectReader mapper = new CsvMapper().reader(Person.class).with(schema);
MappingIterator<Person> it = mapper.readValues(str);
while (it.hasNext()) {
list.add(it.next());
}
System.out.println("stored list is:" + (list != null ? list.toString() : null));
}}
Most of the libraries I found on google are for csv files but I am
working with google app engine so can't write or read files
You can read file (in project file system).
You can read and write file in blobstore, google cloud storage
Use a Tokenizer to split the string into objects then set them to the object.
//Split the string into tokens using a comma as the token seperator
StringTokenizer st = new StringTokenizer(lineFromFile, ",");
while (st.hasMoreTokens())
{
//Collect each item
st.nextElement();
}
//Set to object
Person p = new Person(item1, item2, item3);
If the columns can be reversed, you parse the header line, save it's values and and use it to decide which column each token falls under using, say, a Map
String columns[] = new String[3]; //Fill these with column names
Map<String,String> map = new HashMap<>();
int i=0;
while (st.hasMoreTokens())
{
//Collect each item
map.put(columns[i++], st.nextElement());
}
Then just, create the Person
Person p = new Person(map.get("COL1"), map.get("COL2"), map.get("COL3"));
In Python we can do this easily:
data = {'name':'Felix'}
s = 'Hello, %(name)s' % data
s
'Hello, Felix'
Is there a similar way in Java to implement the same thing?
PS:
Sorry for the unclear question. the use case is : we have a map which stores the key-values, the Template only need to specify a key in the map, then the value of the key will be in the place where the key is in the template.
AFAIK you can use String#format for this:
String name = "Felix";
String s = String.format("Hello, %s", name);
System.out.println(s);
This will print
Hello, Felix
More info about how to use the formatting of String#format can be found on java.util.Formatter syntax
You want String.format method.
String data = "Hello, %s";
String updated = String.format(data, "Felix");
If you want to replace only Strings with Strings then code from second part of my answer will be better
Java Formatter class doesn't support %(key)s form, but instead you can use %index$s where index is counted from 1 like in this example
System.out.format("%3$s, %2$s, %1s", "a", "b", "c");
// indexes 1 2 3
output:
c, b, a
So all you need to do is create some array that will contain values used in pattern and change key names to its corresponding indexes (increased by 1 since first index used by Formatter is written as 1$ not as 0$ like we would expect for arrays indexes).
Here is example of method that will do it for you
// I made this Pattern static and put it outside of method to compile it only once,
// also it will match every (xxx) that has % before it, but wont include %
static Pattern formatPattern = Pattern.compile("(?<=%)\\(([^)]+)\\)");
public static String format(String pattern, Map<String, ?> map) {
StringBuffer sb = new StringBuffer();
List<Object> valuesList = new ArrayList<>();
Matcher m = formatPattern.matcher(pattern);
while (m.find()) {
String key = m.group(1);//group 1 contains part inside parenthesis
Object value = map.get(key);
// If map doesn't contain key, value will be null.
// If you want to react somehow to null value like throw some
// Exception
// now is the good time.
if (valuesList.contains(value)) {
m.appendReplacement(sb, (valuesList.indexOf(value) + 1) + "\\$");
} else {
valuesList.add(value);
m.appendReplacement(sb, valuesList.size() + "\\$");
}
}
m.appendTail(sb);
return String.format(sb.toString(), valuesList.toArray());
}
usage
Map<String, Object> map = new HashMap<>();
map.put("name", "Felix");
map.put("age", 70);
String myPattern =
"Hi %(emptyKey)s! My name is %(name)s %(name)s and I am %(age)s years old";
System.out.println(format(myPattern, map));
output:
Hi null! My name is Felix Felix and I am 70 years old
As you can see you can use same key few times (in our case name) and if your map wont contain key used in your String pattern (like emptyKey) it will be replaced with null.
Above version was meant to let you set type of data like s d and so on, but if your data will always be replaced with Strings, then you can skip String.format(sb.toString(), valuesList.toArray()) and replace all your keys with values earlier.
Here is simpler version that will accept only map with <String,String> key-value relationship.
static Pattern stringsPattern = Pattern.compile("%\\(([^)]+)\\)s\\b");
public static String formatStrings(String pattern, Map<String, String> map) {
StringBuffer sb = new StringBuffer();
Matcher m = stringsPattern.matcher(pattern);
while (m.find()) {
// we can't use null as replacement so we need to convert it to String
// first. We can do it with String.valueOf method
m.appendReplacement(sb, String.valueOf(map.get(m.group(1))));
}
m.appendTail(sb);
return sb.toString();
}
Under this use case, you need a template engine like velocity or freemarker to use a Map-like data structure to render a string template, there is no builtin module in java to do that. like this(with velocity):
public static void main(String[] args) {
Context context = new VelocityContext();
context.put("appid", "9876543d1");
context.put("ds", "2013-09-11");
StringWriter sw = new StringWriter();
String template = "APPID is ${appid} and DS is ${ds}";
Velocity.evaluate(context, sw, "velocity", template);
System.out.println(sw.toString());
}
If you want more advanced techniques like i18n support, you can use the advanced Message Format features
ex:
in langage properties files you add the property 'template' wich is your message
template = At {2,time,short} on {2,date,long}, \
we detected {1,number,integer} spaceships on \
the planet {0}.
then you can format your valriables pass the arguments in an array:
Object[] messageArguments = {
"Mars",
new Integer(7),
new Date()
};
You call the formatter it this way:
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(messageArguments);
the detailed example is here
http://docs.oracle.com/javase/tutorial/i18n/format/messageFormat.html