I'm very new to F# and trying to figure out the best way to convert between similar but different data objects while minimising duplication of code. I'm writing an app using the Fabulous F# MVU framework for Xamarin Forms.
I'm using sqlite-net-pcl for data persistence, which calls for an object when creating a database table and interacting with that table, and I have created an AuthorObject
:
type AuthorObject() =
[<PrimaryKey>] // sqlite-net-pcl attribute
member val AuthorId = 0 with get, set
member val Username = "" with get, set
member val Token = "" with get, set
Fabulous makes use of record types for models and I have created an Author
record type to pass around inside Fabulous:
type Author =
{ AuthorId: int
Username: string
Token: string }
But I also have to interact with a web service API library (written in C#) that returns an AuthorDetailDto
object. AuthorDetailDto
has the same named properties on it as above, plus more that I don't need to worry about. I cannot change the definition of AuthorDetailDto
.
So it seems the constraints are currently that sqlite-net-pcl needs an object with members, Fabulous needs a record type, and I have this AuthorDetailDto
object coming across from the service API client library that I can't change. I need to convert between these types and I initially was hoping that I could get away with just:
let convertToObject author = // Was hoping author param here could be generic in some way
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
But with the added complication of AuthorDetailDto
, I'm finding I now need to do something like this to keep the compiler happy:
let convertAuthorDetailDtoToObject (dto: AuthorDetailDto) =
let obj = AuthorObject()
obj.AuthorId <- dto.AuthorId
obj.Username <- dto.Username
obj.Token <- dto.Token
obj
let convertAuthorToObject (author: Author) =
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
Here are some source files:
DomainModels.fs:
namespace MyApp.Mobile
module DomainModels =
type Author =
{ AuthorId: int
Username: string
Token: string }
Author.fs:
namespace MyApp.Mobile.Repository
open SQLite
open Client // Provides AuthorDetailDto.
open MyApp.Mobile.DomainModels
module Author =
type AuthorObject() =
[<PrimaryKey>]
member val AuthorId = 0 with get, set
member val Username = "" with get, set
member val Token = "" with get, set
let convertToObject author =
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
let connect dbPath = async {
let db = SQLiteAsyncConnection(SQLiteConnectionString dbPath)
do! db.CreateTableAsync<AuthorObject>() |> Async.AwaitTask |> Async.Ignore
return db
}
let purgeLoggedInAuthor dbPath = async {
let! db = connect dbPath
do! db.ExecuteAsync "DELETE FROM AuthorObject" |> Async.AwaitTask |> Async.Ignore
}
let insertLoggedInAuthor dbPath author = async {
let! db = connect dbPath
do! db.InsertAsync (convertToObject author) |> Async.AwaitTask |> Async.Ignore
}
The initial call looks like this:
insertLoggedInAuthor dbPath loggedInAuthor // loggedInAuthor is an AuthorDetailDto.
The error is FS0001 This expression was expected to have type 'DomainModels.Author' but here has type 'AuthorDetailDto'
due to both types being referenced in Author.fs.
I've tried messing around with inline
and static member constraints, and attempting to define an interface, but due to Author
needing to be a record type and not being able to change AuthorDetailDto
, it doesn't seem like this approach is viable. Or perhaps I'm just not well versed enough in F#.
To those more familiar with F# than me, what is the best way to address this kind of situation to minimise code duplication?
question from:
https://stackoverflow.com/questions/65894470/converting-data-objects-f-fabulous-sql-net-pcl-and-a-web-api-client-library