Tuesday 26 April 2011

Coercion Trap

Today I've been helping a friend with a very simple process to migrate some data from a CSV file to a database.

The process reads a csv file and each line became a list of values. Then it turns each list of values into an object by coercion. It seemed really simple to us but then a silly problem showed up. Let me explain what happened:

Given a list of values formed as a csv file lines, let's read one by one and try to convert them into a valid object. For example:


class CoercionTrap {

static class Person{
String name
String age

Person(name,age){
this.name = name
this.age = age
}
}

static main(args){
def peopleList = []
def csvValues = ["John|23","Robert|34","Silvia|"]

csvValues.each{p->
def person = p.toString().tokenize('|') as Person
peopleList << person
}

peopleList.each{
println it.name
}
}
}


In this code we have three group of values:
  • John|23
  • Robert|34
  • Silvia|
We try to make each group to become a Person by list coercion. Person has name, and age so we create a constructor in order to fit each pair of values.

But when we try to execute this code, it crash eventually. Why? because the third pair of values doesn't contain two values, just one (Silvia) and at runtime Groovy claims it couldn't find a constructor with just one entry.

No problem, let's add one more constructor with just one argument:


class CoercionTrap {

static class Person{
String name
String age

Person(name,age){
this.name = name
this.age = age
}

Person(name){
this.name = name
}
}

static main(args){
def peopleList = []
def csvValues = ["John|23","Robert|34","Silvia|"]

csvValues.each{p->
def person = p.toString().tokenize('|') as Person
peopleList << person
}
peopleList.each{
println it.name
}
}
}

Now if we execute the example, yes it will execute smoothly... but look at the console:

[John, 23]
[Robert, 34]
[Silvia]

The code doesn't print the name of each person as expected but an array of values. What happened here?

The problem is that the last constructor can handle both String and String arrays instances. If we want the constructor to filter the arrays just help the compiler to check the instance by adding the static type:

//...
static class Person{
String name
String age

Person(name,age){
this.name = name
this.age = age
}
Person(String name){
this.name = name
}
}

//...


Now executing the code will output the expected result:

John
Robert
Silvia

No comments:

Post a Comment