Overview
Mappers are extension functions that convert Data Transfer Objects (DTOs) from the API layer into domain models. This separation ensures that the domain layer remains independent of external API contracts and naming conventions.
ProductMapper
Collection of extension functions for mapping product-related DTOs to domain models.
Location: data/mapper/ProductMapper.kt
Purpose
The mapper ensures:
Domain layer independence from API structure changes
Type-safe transformations with compile-time guarantees
Centralized mapping logic for easier maintenance
Clean separation between network and business logic
Mapping Functions
toDomain (Product)
Converts a ResultsDto from the search API into a simplified Product model for list display.
fun ResultsDto . toDomain (): Product
Receiver: ResultsDto - DTO returned by the search endpoint
Returns: Product - Simplified domain model for product lists
Implementation: ProductMapper.kt:22
Implementation
Field Mapping
fun ResultsDto . toDomain (): Product {
return Product (
id = this .id,
title = this .name,
domainId = this .domainId,
lastUpdated = this .lastUpdated,
thumbnail = this .pictures. firstOrNull ()?.url
)
}
Example Usage
val dto = ResultsDto (
id = "MCO123456" ,
catalogProductId = "MCO-CAT-789" ,
domainId = "LAPTOPS" ,
name = "Laptop Dell Inspiron 15" ,
attributes = arrayListOf (),
shortDescription = null ,
pictures = arrayListOf (
PicturesDto (url = "https://http2.mlstatic.com/.../I.jpg" )
),
lastUpdated = "2026-03-04T10:30:00Z"
)
val product = dto. toDomain ()
println (product)
// Product(
// id = "MCO123456",
// title = "Laptop Dell Inspiron 15",
// thumbnail = "https://http2.mlstatic.com/.../I.jpg",
// domainId = "LAPTOPS",
// lastUpdated = "2026-03-04T10:30:00Z",
// attributes = []
// )
The thumbnail field uses firstOrNull() to safely handle products with no images, returning null instead of throwing an exception.
toDetailDomain (ProductDetail)
Converts a ResultsDto into a detailed ProductDetail model with full attributes and image gallery.
fun ResultsDto . toDetailDomain (): ProductDetail
Receiver: ResultsDto - DTO with complete product information
Returns: ProductDetail - Full domain model for detail screen
Implementation: ProductMapper.kt:38
Implementation
Field Mapping
fun ResultsDto . toDetailDomain (): ProductDetail {
return ProductDetail (
id = this .id,
title = this .name,
pictures = this .pictures. map { it.url },
attributes = this .attributes. map { it. toDomain () },
description = this .shortDescription?.content
)
}
Example Usage
val dto = ResultsDto (
id = "MCO123456" ,
catalogProductId = "MCO-CAT-789" ,
domainId = "LAPTOPS" ,
name = "Laptop Dell Inspiron 15" ,
attributes = arrayListOf (
AttributesDto (
id = "BRAND" ,
name = "Marca" ,
valueName = "Dell"
),
AttributesDto (
id = "MODEL" ,
name = "Modelo" ,
valueName = "Inspiron 15 3000"
)
),
shortDescription = ShortDescriptionDto (
content = "Laptop de alta performance para trabajo y entretenimiento"
),
pictures = arrayListOf (
PicturesDto (url = "https://http2.mlstatic.com/.../I.jpg" ),
PicturesDto (url = "https://http2.mlstatic.com/.../II.jpg" )
),
lastUpdated = "2026-03-04T10:30:00Z"
)
val detail = dto. toDetailDomain ()
println (detail)
// ProductDetail(
// id = "MCO123456",
// title = "Laptop Dell Inspiron 15",
// pictures = [
// "https://http2.mlstatic.com/.../I.jpg",
// "https://http2.mlstatic.com/.../II.jpg"
// ],
// attributes = [
// ProductAttribute(id = "BRAND", name = "Marca", value = "Dell"),
// ProductAttribute(id = "MODEL", name = "Modelo", value = "Inspiron 15 3000")
// ],
// description = "Laptop de alta performance para trabajo y entretenimiento"
// )
The mapper transforms nested DTOs (attributes, pictures) into simple domain types, flattening the structure for easier UI consumption.
toDomain (ProductAttribute)
Converts an AttributesDto into a ProductAttribute domain model.
fun AttributesDto . toDomain (): ProductAttribute
Receiver: AttributesDto - Technical attribute from the API
Returns: ProductAttribute - Simplified domain attribute
Implementation: ProductMapper.kt:54
Implementation
Field Mapping
fun AttributesDto . toDomain (): ProductAttribute {
return ProductAttribute (
id = this .id,
name = this .name,
value = this .valueName
)
}
Example Usage
Direct Usage
Bulk Transformation
val attributeDto = AttributesDto (
id = "BRAND" ,
name = "Marca" ,
valueName = "Dell"
)
val attribute = attributeDto. toDomain ()
println (attribute)
// ProductAttribute(
// id = "BRAND",
// name = "Marca",
// value = "Dell"
// )
The valueName field in AttributesDto is nullable. If null, the domain value will also be null. Always handle this case in the UI layer.
Domain Models
Product
Simplified product model for list display.
data class Product (
val id: String ,
val title: String ,
val thumbnail: String ? = null ,
val domainId: String ? = null ,
val lastUpdated: String ? = null ,
val attributes: List < ProductAttribute > = emptyList ()
)
Location: domain/model/Product.kt:15
Unique product identifier
URL of the product thumbnail image
Category or domain identifier (e.g., “LAPTOPS”)
ISO 8601 timestamp of last update
List of product specifications (default: empty list)
ProductDetail
Detailed product model for the detail screen.
data class ProductDetail (
val id: String ,
val title: String ,
val pictures: List < String > = emptyList (),
val attributes: List < ProductAttribute > = emptyList (),
val description: String ? = null
)
Location: domain/model/ProductDetail.kt:14
Unique product identifier
Full product name or title
List of product image URLs for the gallery
Complete list of technical specifications
ProductAttribute
Product specification or technical attribute.
data class ProductAttribute (
val id: String ,
val name: String ,
val value : String ?
)
Attribute identifier (e.g., “BRAND”, “COLOR”)
Human-readable attribute name (e.g., “Marca”, “Color”)
Human-readable attribute value (e.g., “Dell”, “Negro”)
Mapping Patterns
Safe Navigation
All mappers use safe navigation operators to handle nullable fields:
// Extract first picture URL safely
thumbnail = this .pictures. firstOrNull ()?.url
// Handle nullable description
description = this .shortDescription?.content
Collection Mapping
Use Kotlin’s map function for transforming collections:
// Map pictures to URLs
pictures = this .pictures. map { it.url }
// Map attributes to domain models
attributes = this .attributes. map { it. toDomain () }
Null Handling Strategy
Nullable vs Default Values
Use nullable types when absence of data has semantic meaning (e.g., thumbnail, description)
Use default values when absence means empty collection (e.g., attributes = emptyList())
Testing Mappers
Example test cases for mapper functions:
Test: toDomain
Test: No Pictures
Test: toDetailDomain
@Test
fun `toDomain maps ResultsDto to Product correctly` () {
// Given
val dto = ResultsDto (
id = "MCO123" ,
catalogProductId = "CAT-123" ,
domainId = "LAPTOPS" ,
name = "Test Laptop" ,
attributes = arrayListOf (),
shortDescription = null ,
pictures = arrayListOf (
PicturesDto (url = "https://example.com/img.jpg" )
),
lastUpdated = "2026-03-04T10:30:00Z"
)
// When
val product = dto. toDomain ()
// Then
assertEquals ( "MCO123" , product.id)
assertEquals ( "Test Laptop" , product.title)
assertEquals ( "https://example.com/img.jpg" , product.thumbnail)
assertEquals ( "LAPTOPS" , product.domainId)
assertEquals ( "2026-03-04T10:30:00Z" , product.lastUpdated)
}
Architecture Benefits
Decoupling Domain models are isolated from API changes. Renaming fields in the API only requires updating mappers.
Type Safety Compile-time guarantees that all required fields are mapped correctly.
Testability Pure functions with no dependencies make testing simple and reliable.
Simplification Complex nested DTOs are flattened into simple domain models optimized for UI.
Repositories How repositories use mappers
Remote APIs DTO definitions from the API layer