Dominic Charley-Roy
https://github.com/dominiccharleyroy
dominic.charley-roy @ mail.mcgill

Dominic,McGill,Bachelor of Science,2015
John,Concordia,Bachelor of Arts,2016
Laura,UQAM,Bachelor of Commerce,2017
1,3,19.00
2,5,18.200
3,6,17.300

CSV lets us optionally use the first row as headers, naming each field/column.
These names are formatted like a regular CSV row.
Name,Program,Age
Dominic,Computer Science,22
Michael,Engineering,22
Jennifer,Math,21
Annie,Management,19
Fred,Art History,20If we have commas in a field, we can wrap the entire field in double-quotes.
This tells CSV parsers that any comma within the double quotes is meant to be part of the data!
Name,Program,Age
"Charley-Roy, Dominic",Computer Science,22
"James, Michael",Engineering,22
"O'Connor, Jennifer",Math,21
"Panini, Annie",Management,19
"Lestrange, Fred",Art History,20To do this, we have to wrap a field in quotes and, for any quote which is part of the data, add a quote in front of it.
Name,Program,Age
"Dominic ""Nickname"" Charley-Roy",Computer Science,22
"Michael ""Nickname"" James",Engineering,22
"Jennifer ""Nickname"" O'Connor",Math,21x|y
1|10
2|20
3|30
Reading from a file using ACC (Apache Commons CSV) is super simple!
The first step is to build a CSVFormat, which will describe the formatting of our file (delimiter, are headers present, etc.).
We then create a CSVParser based on the format and a FileReader.
The CSVReader provides us with a list of CSVRecords!
The default format uses commas as a delimiter, supports double quotes, and does not have the first row as headers.
CSVFormat myFormat = CSVFormat.DEFAULT;To enable a header row, we call a method on the format. This will build a new format.
CSVFormat myFormat = CSVFormat.DEFAULT.withHeaders();We can also hard-code headers instead of putting a header row.
CSVFormat myFormat = CSVFormat.DEFAULT.withHeaders(
"Name", "Age", "Location");If we want to change the delimiter to a pipe (|):
CSVFormat myFormat = CSVFormat.DEFAULT.withDelimiter('|');These can also be chained! Suppose we want the pipe delimiter and a header row:
CSVFormat myFormat = CSVFormat.DEFAULT
.withDelimiter('|')
.withHeader();
There's a lot more options, so I encourage you to check them out! For example, we can have comments in CSV files...
Once we have our format, we can create our CSVParser! This will wrap around a Reader and a CSVFormat.
Note: Creating a FileReader can throw an exception, so we need to make sure to take that into consideration...
CSVParser parser = new CSVParser(
new FileReader("data.csv"), myFormat);When we are done with our CSVParser, we need to close it to prevent memory leaks. This will also close the FileReader!
parser.close();CSVParser makes it very easy to get our data! We simply call getRecords to get a list of our CSVRecords!
Note: If we have a header row, this will not be included in the list if we set up our format correctly!
List<CSVRecord> records = parser.getRecords();Protip: Since this is a list, this is iterable! So we can use the special for-each loop to go over each record!
for (CSVRecord record : parser.getRecords()) {
// ... insert coolness here.
}Each CSVRecord represents a row in our data file! It works like a 0-indexed array of strings, so if we wanted the third field we would do:
String thirdField = record.get(2);If we are using headers, we can also use the header name of the field! Suppose we have a field named grade...
int grade = Integer.parseInt(
record.get("grade"));

Wait no...
We wrap the CSVPrinter around a stream, usually a FileWriter.
CSVPrinter printer = new CSVPrinter(
new FileWriter("data.csv"), myFormat);Again we have to make sure to close the printer when we are done with it to prevent memory leaks (and make sure it saves!)
printer.close();There are two ways to save records! The first is to simply pass in all the arguments as arguments to the printRecord method. This can be done in two ways.
// Method 1
printer.printRecord("Dominic", 22, "McGill");
// Method 2
String[] fields = new String[] {
"Dominic", "23", "McGill"};
printer.printRecord(fields);The second is to record the fields one by one and then add the new line.
printer.print("Dominic");
printer.print(23);
printer.print("McGill");
printer.println();We want to create some kind of general, consistent way to serialize our object. In a game, you may have a lot of object you want to serialize, so this is important...
This is important because field order matters in a CSV file, so we need to be consistent!
public class Person {
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
}public class Course {
private String name;
private int enrolled;
public Course(String name, int enrolled) {
this.name = name;
this.enrolled = enrolled;
}
public String getName() {
return name;
}
public int getEnrolled() {
return enrolled;
}
}This approach has 1 class responsible for all of our serialization. It exposes methods for each class.
public class Serializer {
public CSVParser createParser(FileReader reader) {
// ...
}
public CSVPrinter createPrinter(FileWriter writer) {
// ...
}
public Person readPerson(CSVRecord record){
// ...
}
public void writePerson(Person person, CSVPrinter printer) {
// ...
}
public Course readCourse(CSVRecord record){
// ...
}
public void writeCourse(Course course, CSVPrinter printer) {
// ...
}
}public Person readPerson(CSVRecord record){
return new Person(
record.get(0),
record.get(1),
Integer.parseInt(record.get(2)));
}
public void writePerson(Person person, CSVPrinter printer)
throws IOException {
printer.printRecord(
person.getFirstName(),
person.getLastName(),
person.getAge());
} This approach makes use of generics to define an interface which class-specific serializers will implement.
public interface CSVSerializer<C> {
public C read(CSVRecord record);
public void write(CSVPrinter printer, C object)
throws IOException;
}public class PersonSerializer implements CSVSerializer<Person> {
@Override
public Person read(CSVRecord record) {
return new Person(
record.get(0),
record.get(1),
Integer.parseInt(record.get(2)));
}
@Override
public void write(CSVPrinter printer, Person person) throws IOException {
printer.printRecord(
person.getFirstName(),
person.getLastName(),
person.getAge());
}
}
Now that you are considering saving data potentially input by a user, there are some things you need to check for...