Skip to main content

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

Transformation Logic

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

Transformation Logic

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

Transformation Logic

fun AttributesDto.toDomain(): ProductAttribute {
    return ProductAttribute(
        id = this.id,
        name = this.name,
        value = this.valueName
    )
}

Example Usage

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
id
String
required
Unique product identifier
title
String
required
Product name or title
thumbnail
String
URL of the product thumbnail image
domainId
String
Category or domain identifier (e.g., “LAPTOPS”)
lastUpdated
String
ISO 8601 timestamp of last update
attributes
List<ProductAttribute>
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
id
String
required
Unique product identifier
title
String
required
Full product name or title
pictures
List<String>
List of product image URLs for the gallery
attributes
List<ProductAttribute>
Complete list of technical specifications
description
String
Product description text

ProductAttribute

Product specification or technical attribute.
data class ProductAttribute(
    val id: String,
    val name: String,
    val value: String?
)
id
String
required
Attribute identifier (e.g., “BRAND”, “COLOR”)
name
String
required
Human-readable attribute name (e.g., “Marca”, “Color”)
value
String
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

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
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

Build docs developers (and LLMs) love