trait FieldConverter { def convert(a: A, b: B):Any }The companion object for the CaseClassConverter supplied a default FieldConverter if you didn't need one:
object CaseClassConverter { def apply(converter: => FieldConverter = defaultFieldConverter) class DefaultFieldConverter extends FieldConverter { def convert(a: A, b: B):Any = { b } } }Ye gads look at the boilerplate! All to wrap a simple function! And yet there is huge scope to still get it wrong, because if you do actually supply a custom FieldConverter, it actually ends up being you who handles all conversions from then on, even though you really don't care about most of them. So you need an if for everything to work properly:
class MyFieldConverter extends FieldConverter { def convert(a: A, b: B):Any = { if (specialCase) { ... // do special conversion } else { // Do the normal conversion b } } }
That sucks.
So, following a nice little nugget I found in Effective Scala, I refactored the whole extension mechanism into using PartialFunctions, like this:
Make FieldConverter a type alias
type FieldConverter = PartialFunction[(Type, Any), Any]
Chain up a user's custom FieldConverter with the default one
val exhaustiveConverter:FieldConverter = userConverter orElse defaultConverterScala will check whether the userConverter is defined at a given input - if not, it'll fall through to the defaultConverter - perfect.
Now custom converters are simple case blocks
val alwaysMakeJavaLongsIntoInts: FieldConverter = { case (t: Type, v: Any) if (isInt(t) && isJLong(v.getClass)) => { v.asInstanceOf[Long].toInt } }A userConverter only has to worry about converting one type of thing, and doesn't know (or care) about downstream converters. A simplified Chain of Responsibility.