- Do I really need to do it? I came from other languages and best practices may change from one language to another.
I think this comes down to preference. IMO it can be a good idea to not include serialization related information in your domain models. The reasons being:
- It doesn't really belong there. As you said in your question, adding all this mapping logic clutters the
Email
class.
- Changing your CSV or JSON parser might force you to also change the mapping logic in your
Email
class, making your code more coupled.
A clean way to do this is to use extension functions. You can choose to make use of them locally or to put them inside the companion object. The latter option would still clutter your data class with mapping logic.
So if you have for example a CsvEmailImporter
class, then you could write a private function that converts a line in the CSV file into an Email
object:
class CsvEmailImporter : EmailImporter {
override fun import(source: InputStream): List<Email> {
val emails = source.bufferedReader()
.lineSequence()
.drop(1) // don't include the csv header
.map { it.toEmail() }
.toList()
return emails
}
private fun String.toEmail(): Email {
// map a line in the CSV file to an Email object
}
}
Notice how in this example our mapping function is private to the CsvEmailImporter
. This means we didn't have to modify the Email
class itself. It also means that you can't access or see it from anywhere else.
- If so, what is the best way to do it. I would like a link to some implementations in well-known libraries.
I don't know if there's a "best way" here but I have found this example from a popular manga reader app.
In this case there's a top-level function to convert Manga
objects to MangaInfo
objects. This function is accessible from the outside and is similar to putting it inside the companion object.
I would suggest trying out different approaches to see what works best for you and your codebase.