# JSON Views

## Overview

JSON Views are responsible for transforming Maestro output data into structured JSON responses for API endpoints. They handle data serialization, format consistency, and provide clean interfaces between business logic and API consumers.

## Purpose

JSON Views serve several key functions:
- **Data Serialization** - Transform entities and data into JSON format
- **Response Structure** - Provide consistent API response formats
- **Data Filtering** - Control which fields are included in responses
- **Error Formatting** - Structure error responses consistently
- **API Versioning Support** - Handle different API response formats

## Structure

Each JSON view typically follows this pattern:

```go
type ListPlantsJsonView struct {
    PlantSerializer *serializers.PlantSerializer
    Kit             *kit.Kit
}

func (this *ListPlantsJsonView) H(out *maes.ListPlantsMaeOut) (map[string]interface{}, error) {
    response := make(map[string]interface{})

    // Serialize main data
    plants, err := this.PlantSerializer.SerializeMany(out.Plants, "list")
    if err != nil {
        return nil, fmt.Errorf("failed to serialize plants: %w", err)
    }

    response["plants"] = plants
    response["pagination"] = this.serializePagination(out.Pagination)

    return response, nil
}
```

## Core Methods

### Main Serialization Method

```go
func (this *ShowPlantJsonView) H(out *maes.ShowPlantMaeOut) (map[string]interface{}, error) {
    plant, err := this.PlantSerializer.SerializeOne(out.Plant, "show")
    if err != nil {
        return nil, fmt.Errorf("failed to serialize plant: %w", err)
    }

    response := map[string]interface{}{
        "plant": plant,
    }

    if out.RelatedData != nil {
        response["related"] = this.serializeRelatedData(out.RelatedData)
    }

    return response, nil
}
```

### Collection Serialization

```go
func (this *ListPlantsJsonView) serializePlants(plants []*mdl.Plant) ([]map[string]interface{}, error) {
    var serialized []map[string]interface{}

    for _, plant := range plants {
        plantData, err := this.PlantSerializer.SerializeOne(plant, "list")
        if err != nil {
            return nil, fmt.Errorf("failed to serialize plant %s: %w", plant.Id, err)
        }
        serialized = append(serialized, plantData)
    }

    return serialized, nil
}
```

### Metadata Serialization

```go
func (this *ListPlantsJsonView) serializePagination(pagination *common.Pagination) map[string]interface{} {
    if pagination == nil {
        return nil
    }

    return map[string]interface{}{
        "current_page": pagination.CurrentPage,
        "per_page":     pagination.PerPage,
        "total":        pagination.Total,
        "total_pages":  pagination.TotalPages,
        "has_next":     pagination.HasNext,
        "has_prev":     pagination.HasPrev,
    }
}
```

## Response Patterns

### Success Responses

```go
// Single entity response
func (this *ShowPlantJsonView) H(out *maes.ShowPlantMaeOut) (map[string]interface{}, error) {
    plant, err := this.PlantSerializer.SerializeOne(out.Plant, "show")
    if err != nil {
        return nil, err
    }

    return map[string]interface{}{
        "success": true,
        "data":    plant,
    }, nil
}

// Collection response
func (this *ListPlantsJsonView) H(out *maes.ListPlantsMaeOut) (map[string]interface{}, error) {
    plants, err := this.PlantSerializer.SerializeMany(out.Plants, "list")
    if err != nil {
        return nil, err
    }

    return map[string]interface{}{
        "success":    true,
        "data":       plants,
        "pagination": this.serializePagination(out.Pagination),
    }, nil
}
```

### Error Responses

```go
func (this *JsonViewHelper) ErrorResponse(err error, code string) map[string]interface{} {
    response := map[string]interface{}{
        "success": false,
        "error": map[string]interface{}{
            "message": err.Error(),
            "code":    code,
        },
    }

    // Add validation errors if available
    if validationErr, ok := err.(*ValidationError); ok {
        response["error"].(map[string]interface{})["validation"] = validationErr.Fields
    }

    return response
}
```

### Nested Data Serialization

```go
func (this *ShowGardenJsonView) H(out *maes.ShowGardenMaeOut) (map[string]interface{}, error) {
    garden, err := this.GardenSerializer.SerializeOne(out.Garden, "show")
    if err != nil {
        return nil, err
    }

    response := map[string]interface{}{
        "garden": garden,
    }

    // Include related plants if hydrated
    if len(out.Garden.Plants) > 0 {
        plants, err := this.PlantSerializer.SerializeMany(out.Garden.Plants, "embedded")
        if err != nil {
            return nil, fmt.Errorf("failed to serialize plants: %w", err)
        }
        response["garden"].(map[string]interface{})["plants"] = plants
    }

    // Include user information if available
    if out.Garden.User != nil {
        user, err := this.UserSerializer.SerializeOne(out.Garden.User, "basic")
        if err != nil {
            return nil, fmt.Errorf("failed to serialize user: %w", err)
        }
        response["garden"].(map[string]interface{})["user"] = user
    }

    return response, nil
}
```

## Serialization Contexts

### Context-Based Serialization

```go
func (this *PlantJsonView) serializePlant(plant *mdl.Plant, context string) (map[string]interface{}, error) {
    switch context {
    case "list":
        return this.PlantSerializer.SerializeOne(plant, "list")
    case "show":
        return this.PlantSerializer.SerializeOne(plant, "show")
    case "embedded":
        return this.PlantSerializer.SerializeOne(plant, "embedded")
    default:
        return this.PlantSerializer.SerializeOne(plant, "basic")
    }
}
```

### Field Selection

```go
func (this *PlantJsonView) serializeWithFields(plant *mdl.Plant, fields []string) (map[string]interface{}, error) {
    if len(fields) == 0 {
        return this.PlantSerializer.SerializeOne(plant, "show")
    }

    // Create custom serialization based on requested fields
    result := make(map[string]interface{})

    for _, field := range fields {
        switch field {
        case "id":
            result["id"] = plant.Id
        case "name":
            result["name"] = plant.Name
        case "species":
            result["species"] = plant.Species
        case "size":
            result["size"] = plant.Size
        case "created_at":
            result["created_at"] = plant.CreatedAt.Format(time.RFC3339)
        }
    }

    return result, nil
}
```

## Integration with Controllers

JSON Views are typically used in API controller methods:

```go
func (gc *GardenController) ApiList(c *gin.Context) {
    input := &maes.ListGardensMaeIn{}

    // Bind query parameters
    if err := gc.ListGardensBinder.Bind(c.Request.Context(), input, c); err != nil {
        c.JSON(400, gc.ErrorJsonView.ValidationError(err))
        return
    }

    // Execute business logic
    output, err := gc.ListGardensMae.Execute(c.Request.Context(), input)
    if err != nil {
        c.JSON(500, gc.ErrorJsonView.ServerError(err))
        return
    }

    // Serialize response
    response, err := gc.ListGardensJsonView.H(output)
    if err != nil {
        c.JSON(500, gc.ErrorJsonView.SerializationError(err))
        return
    }

    c.JSON(200, response)
}
```

## Error Handling

### Serialization Error Recovery

```go
func (this *PlantJsonView) H(out *maes.ShowPlantMaeOut) (map[string]interface{}, error) {
    plant, err := this.PlantSerializer.SerializeOne(out.Plant, "show")
    if err != nil {
        // Fallback to basic serialization
        plant = map[string]interface{}{
            "id":   out.Plant.Id,
            "name": out.Plant.Name,
            "error": "Serialization incomplete",
        }
    }

    return map[string]interface{}{
        "plant": plant,
    }, nil
}
```

### Partial Failure Handling

```go
func (this *ListPlantsJsonView) H(out *maes.ListPlantsMaeOut) (map[string]interface{}, error) {
    var plants []map[string]interface{}
    var errors []string

    for _, plant := range out.Plants {
        plantData, err := this.PlantSerializer.SerializeOne(plant, "list")
        if err != nil {
            errors = append(errors, fmt.Sprintf("Failed to serialize plant %s: %v", plant.Id, err))
            // Include minimal data
            plantData = map[string]interface{}{
                "id":    plant.Id,
                "name":  plant.Name,
                "error": "Serialization failed",
            }
        }
        plants = append(plants, plantData)
    }

    response := map[string]interface{}{
        "plants": plants,
    }

    if len(errors) > 0 {
        response["warnings"] = errors
    }

    return response, nil
}
```

## API Versioning

### Version-Specific Responses

```go
func (this *PlantJsonView) H(out *maes.ShowPlantMaeOut) (map[string]interface{}, error) {
    version := this.getAPIVersion(out.Context)

    switch version {
    case "v1":
        return this.serializeV1(out.Plant)
    case "v2":
        return this.serializeV2(out.Plant)
    default:
        return this.serializeLatest(out.Plant)
    }
}

func (this *PlantJsonView) serializeV1(plant *mdl.Plant) (map[string]interface{}, error) {
    return map[string]interface{}{
        "id":      plant.Id,
        "name":    plant.Name,
        "species": plant.Species,
    }, nil
}

func (this *PlantJsonView) serializeV2(plant *mdl.Plant) (map[string]interface{}, error) {
    return map[string]interface{}{
        "id":         plant.Id,
        "name":       plant.Name,
        "species":    plant.Species,
        "size":       plant.Size,
        "perennial":  plant.Perennial,
        "created_at": plant.CreatedAt.Format(time.RFC3339),
    }, nil
}
```

## Performance Optimization

### Lazy Loading

```go
func (this *ListPlantsJsonView) H(out *maes.ListPlantsMaeOut) (map[string]interface{}, error) {
    // Serialize basic plant data first
    plants := make([]map[string]interface{}, len(out.Plants))

    for i, plant := range out.Plants {
        plants[i] = map[string]interface{}{
            "id":   plant.Id,
            "name": plant.Name,
        }
    }

    response := map[string]interface{}{
        "plants": plants,
    }

    // Add detailed data if requested
    if out.Context.IncludeDetails {
        for i, plant := range out.Plants {
            detailed, err := this.PlantSerializer.SerializeOne(plant, "detailed")
            if err == nil {
                plants[i] = detailed
            }
        }
    }

    return response, nil
}
```

## Testing JSON Views

```go
func TestShowPlantJsonView_H(t *testing.T) {
    view := setupShowPlantJsonView()

    output := &maes.ShowPlantMaeOut{
        Plant: &mdl.Plant{
            Id:      "plant123",
            Name:    "Tomato",
            Species: "Solanum lycopersicum",
        },
    }

    response, err := view.H(output)

    assert.NoError(t, err)
    assert.Equal(t, "plant123", response["plant"].(map[string]interface{})["id"])
    assert.Equal(t, "Tomato", response["plant"].(map[string]interface{})["name"])
}

func TestListPlantsJsonView_ErrorHandling(t *testing.T) {
    view := setupListPlantsJsonView()

    output := &maes.ListPlantsMaeOut{
        Plants: []*mdl.Plant{
            {Id: "plant1", Name: "Valid Plant"},
            {Id: "plant2", Name: ""}, // This might cause serialization issues
        },
    }

    response, err := view.H(output)

    assert.NoError(t, err)
    assert.Len(t, response["plants"], 2)
}
```

## Best Practices

### Consistency
- Use consistent response structures across all endpoints
- Maintain consistent field naming conventions
- Apply consistent error response formats

### Performance
- Avoid N+1 queries in serialization
- Use appropriate serialization contexts
- Implement caching for expensive serializations

### Maintainability
- Keep views focused on serialization logic
- Use serializers for complex data transformation
- Handle errors gracefully with fallbacks

### API Design
- Follow RESTful response conventions
- Support field selection when appropriate
- Provide clear error messages and codes

JSON Views provide clean, consistent API responses while maintaining flexibility and performance for various API consumption patterns.