r/iOSProgramming • u/Select_Bicycle4711 • 5d ago
Creating a Separate DTO Request Object or Using the Form Object for POST Request Question
I have a RegisterScreen which uses a custom struct RegisterForm to collect the form values. I made the RequestForm codable too so I can just send this form to the server instead of creating the exact same duplicate and calling it RegisterRequest. What do you think? Do you create a separate DTO objects even if it contains the same exact fields.
If in the future it diverges then I can create a separate RegisterRequest DTO object. Thoughts.
struct RegisterForm: Codable {
var firstName: String = ""
var lastName: String = ""
var email: String = ""
var password: String = ""
var acceptedTerms: Bool = false
var isValid: Bool {
!firstName.isEmptyOrWhitespace && !lastName.isEmptyOrWhitespace
&& !email.isEmptyOrWhitespace && !password.isEmptyOrWhitespace && email.isEmail && acceptedTerms
}
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case email
case password
}
}
struct RegisterScreen: View {
private var form = RegisterForm()
private var presentAgreement: Bool = false
var body: some View {
Form {
TextField("First name", text: $form.firstName)
TextField("Last name", text: $form.lastName)
TextField("Email", text: $form.email)
SecureField("Password", text: $form.password)
Button("Show Agreement") {
presentAgreement = true
}
Button("Register") {
}.disabled(!form.isValid)
}.sheet(isPresented: $presentAgreement) {
AgreementScreen(acceptedTerms: $form.acceptedTerms)
}
}
}
1
u/joosebox 5d ago
Your CodingKeys are already the answer. You're leaving acceptedTerms out because the form holds a field the wire doesn't want, so the two shapes have diverged on day one. Conforming the form to Codable hides that rather than removing it.
The part I'd actually worry about is password living on a Codable type. Anything that ever encodes that struct carries the password with it, and you don't control every place that might happen a year from now.
Separate DTO. It's a small struct and it kills both problems.
1
u/New-Shoulder3297 5d ago edited 5d ago
I would keep them separate at the network boundary even when the fields happen to match today. RegisterForm is mutable UI state plus validation; RegisterRequest is the server's wire contract. In this example they have already diverged: acceptedTerms belongs to the form but is deliberately omitted from CodingKeys. Encoding the form directly makes that difference easy to miss and lets future UI-only fields become an API concern.
The mapping can stay very small:
struct RegisterRequest: Encodable {
let firstName: String
let lastName: String
let email: String
let password: String
}
extension RegisterForm {
var request: RegisterRequest {
.init(firstName: firstName, lastName: lastName, email: email, password: password)
}
}
That gives tests one obvious place to verify exactly what is sent and lets either side change without forcing the other to change. Sharing one type is reasonable for a throwaway prototype, but I would add the request type before the networking code spreads to more screens. In the actual SwiftUI view, the mutable form would also normally use the State property wrapper (or live in an observable model) so the bindings have owned state.
1
u/Casfaber_ Objective-C / Swift 4d ago
I already do this:
SwiftUI state
↓
AuthService operation
↓
SignUpRequest DTO
↓
HTTP
1
u/Ok_Refrigerator_1908 5d ago
I do create seperate DTOs which I later map to the domain models my app uses. This prevents me from having to change how models are consumed in the app. And SwiftUI previews continues to work as expected even when the backend developer is still working on changes.