# Best Practices for Organizing Multiple Datasets in an Apache Ossie Semantic Model

> Organize multiple datasets in Apache Ossie semantic models using best practices. Store data, distinguish types, and validate with JSON schema for a lossless Salesforce roundtrip.

- Repository: [The Apache Software Foundation/ossie](https://github.com/apache/ossie)
- Tags: best-practices
- Published: 2026-07-26

---

**Store each dataset in the `semanticDataObjects` array within a single `semantic_model` entry, distinguish standard from shared datasets using `table_type`, and validate against the JSON schema before conversion to ensure lossless round-trip mapping with Salesforce.**

Apache Ossie provides a semantic modeling framework that standardizes how datasets translate to Salesforce semantic data objects. When organizing multiple datasets within a single Ossie semantic model, adhering to the architectural constraints defined in [`DatasetMappingHandler.java`](https://github.com/apache/ossie/blob/main/DatasetMappingHandler.java) ensures seamless bidirectional conversion and prevents data loss during synchronization.

## Understanding the Semantic Model Structure

An Ossie semantic model is a top-level JSON object containing a **`semantic_model`** array. According to [`ConverterConstants.java`](https://github.com/apache/ossie/blob/main/ConverterConstants.java), most implementations use a single entry in this array unless managing multi-tenant deployments.

Each entry contains a **`semanticDataObjects`** array where individual datasets reside. The `DatasetMappingHandler` treats each element in this array as a distinct dataset during conversion, mapping them to Salesforce semantic data objects.

## Storing Multiple Datasets in the semanticDataObjects Array

The `DatasetMappingHandler` expects datasets under the key **`semanticDataObjects`**. This structure guarantees that the conversion engine can map each dataset to Salesforce and back without information loss.

When organizing multiple datasets:

1. Create a single `semantic_model` entry for typical use cases.
2. Populate the `semanticDataObjects` list with dataset definitions.
3. Define **dimensions** using `semanticDimensions` and **measurements** using `semanticMeasurements` for each dataset.

```java
import org.apache.ossie.util.DataStructureUtils;
import java.util.*;

Map<String, Object> semanticModel = new LinkedHashMap<>();

// Dataset 1: Customers
Map<String, Object> customers = new LinkedHashMap<>();
customers.put("apiName", "Customer");
customers.put("label", "Customer");
customers.put("table_type", "Standard");

List<Map<String, Object>> custDims = new ArrayList<>();
custDims.add(DataStructureUtils.mapOf(
        "apiName", "CustomerId",
        "label", "Customer ID",
        "semanticType", "string"));
customers.put("semanticDimensions", custDims);

List<Map<String, Object>> custMeas = new ArrayList<>();
custMeas.add(DataStructureUtils.mapOf(
        "apiName", "Revenue",
        "label", "Revenue",
        "semanticType", "decimal"));
customers.put("semanticMeasurements", custMeas);

// Dataset 2: Orders
Map<String, Object> orders = new LinkedHashMap<>();
orders.put("apiName", "Order");
orders.put("label", "Order");
orders.put("table_type", "Standard");

List<Map<String, Object>> orderDims = new ArrayList<>();
orderDims.add(DataStructureUtils.mapOf(
        "apiName", "OrderId",
        "label", "Order ID",
        "semanticType", "string"));
orders.put("semanticDimensions", orderDims);

List<Map<String, Object>> orderMeas = new ArrayList<>();
orderMeas.add(DataStructureUtils.mapOf(
        "apiName", "Amount",
        "label", "Amount",
        "semanticType", "decimal"));
orders.put("semanticMeasurements", orderMeas);

// Assembly
List<Map<String, Object>> dataObjects = Arrays.asList(customers, orders);
semanticModel.put("semanticDataObjects", dataObjects);

Map<String, Object> root = new LinkedHashMap<>();
root.put("semantic_model", Collections.singletonList(semanticModel));

```

## Distinguishing Standard and Shared Datasets

Ossie differentiates between **standard** and **shared** (non-standard) datasets using the `table_type` field. Standard datasets carry `table_type = "Standard"` or rely on defaults applied by the **`applyDefaults`** method in [`DatasetMappingHandler.java`](https://github.com/apache/ossie/blob/main/DatasetMappingHandler.java).

Non-standard datasets require special handling. The converter stores these in the top-level **`custom_extensions`** field via the **`storeSharedEntitiesInCustomExtensions`** method. This separation allows the converter to apply Salesforce defaults only to standard datasets while preserving custom extensions for shared entities.

## Handling Custom Extensions and Metadata

Properties that do not map to known Ossie fields belong in **`custom_extensions`**. The [`CustomExtensionHandler.java`](https://github.com/apache/ossie/blob/main/CustomExtensionHandler.java) class manages these properties, moving unmapped keys into `custom_extensions` during conversion to prevent data loss and maintain extensibility.

```java
Map<String, Object> customExt = new LinkedHashMap<>();
customExt.put("vendorTag", "VIP");

// Attach to dataset
customers.put("custom_extensions", customExt);

```

When converting back to Ossie, the handler automatically restores these extensions from the Salesforce representation.

## Applying Platform Defaults After Mapping

After mapping datasets to the target structure, the **`applyDefaults`** method in [`DatasetMappingHandler.java`](https://github.com/apache/ossie/blob/main/DatasetMappingHandler.java) injects required fields such as `label` and `table_type` when they are missing. This guarantees that the generated Salesforce model remains valid while keeping the Ossie definition concise.

Invoke this step after dataset mapping but before schema validation to ensure completeness.

## Maintaining Clean Mapping Configurations

Use the **`MappingUtils.filterMappingsByPrefix`** utility to isolate mappings targeting specific dataset arrays. This approach keeps the conversion pipeline flat and simplifies maintenance when converting between `datasets` and `semanticDataObjects`.

For example, filter mappings with the prefix `datasets` to handle Ossie-to-Salesforce conversion, and use `semanticDataObjects` for the reverse direction.

## Validating Against the JSON Schema

Before persisting or converting a semantic model, run **`SchemaValidator.validateSemanticModel`** against the [`salesforce-semantic-model-schema.json`](https://github.com/apache/ossie/blob/main/salesforce-semantic-model-schema.json) schema. This validation catches structural errors early and ensures compatibility with downstream connectors.

```java
SchemaValidator.validateSemanticModel(root);

```

Validation should occur after applying defaults and before invoking the conversion pipeline.

## When to Use Multiple semantic_model Entries

While the `semantic_model` array supports multiple entries, most Ossie tools assume a single top-level model. Reserve multiple entries for scenarios requiring independent models to coexist, such as multi-tenant deployments. Per `ConverterConstants.SEMANTIC_MODEL`, using a single entry avoids unnecessary complexity for standard use cases.

## Complete Implementation Example

The following example demonstrates building a multi-dataset semantic model with validation and custom extensions:

```java
import org.apache.ossie.converter.*;
import org.apache.ossie.util.*;
import org.apache.ossie.validator.SchemaValidator;
import java.util.*;

public class MultiDatasetModel {
    public static Map<String, Object> buildModel() {
        Map<String, Object> semanticModel = new LinkedHashMap<>();
        
        // Dataset 1: Customers with custom extension
        Map<String, Object> customers = new LinkedHashMap<>();
        customers.put("apiName", "Customer");
        customers.put("label", "Customer");
        customers.put("table_type", "Standard");
        
        Map<String, Object> custExt = new LinkedHashMap<>();
        custExt.put("dataSource", "CRM");
        customers.put("custom_extensions", custExt);
        
        List<Map<String, Object>> custDims = new ArrayList<>();
        custDims.add(DataStructureUtils.mapOf(
            "apiName", "CustomerId",
            "label", "Customer ID",
            "semanticType", "string"));
        customers.put("semanticDimensions", custDims);
        
        List<Map<String, Object>> custMeas = new ArrayList<>();
        custMeas.add(DataStructureUtils.mapOf(
            "apiName", "LifetimeValue",
            "label", "Lifetime Value",
            "semanticType", "decimal"));
        customers.put("semanticMeasurements", custMeas);
        
        // Dataset 2: Orders
        Map<String, Object> orders = new LinkedHashMap<>();
        orders.put("apiName", "Order");
        orders.put("label", "Order");
        orders.put("table_type", "Standard");
        
        List<Map<String, Object>> orderDims = new ArrayList<>();
        orderDims.add(DataStructureUtils.mapOf(
            "apiName", "OrderId",
            "label", "Order ID",
            "semanticType", "string"));
        orders.put("semanticDimensions", orderDims);
        
        List<Map<String, Object>> orderMeas = new ArrayList<>();
        orderMeas.add(DataStructureUtils.mapOf(
            "apiName", "TotalAmount",
            "label", "Total Amount",
            "semanticType", "decimal"));
        orders.put("semanticMeasurements", orderMeas);
        
        // Assembly
        List<Map<String, Object>> dataObjects = Arrays.asList(customers, orders);
        semanticModel.put("semanticDataObjects", dataObjects);
        
        Map<String, Object> root = new LinkedHashMap<>();
        root.put("semantic_model", Collections.singletonList(semanticModel));
        
        // Validation
        SchemaValidator.validateSemanticModel(root);
        
        return root;
    }
}

```

## Summary

- Store datasets in the **`semanticDataObjects`** array within a single `semantic_model` entry for standard use cases.
- Differentiate standard datasets (`table_type = "Standard"`) from shared entities using **`custom_extensions`**.
- Place non-standard properties in **`custom_extensions`** to prevent data loss during conversion.
- Apply Salesforce defaults via **`applyDefaults`** after mapping to ensure required fields are populated.
- Use **`MappingUtils.filterMappingsByPrefix`** to maintain clean mapping configurations.
- Validate models using **`SchemaValidator.validateSemanticModel`** before conversion.
- Reserve multiple `semantic_model` entries for multi-tenant scenarios only.

## Frequently Asked Questions

### How many datasets can I store in one Ossie semantic model?

You can store multiple datasets within a single semantic model by adding them to the `semanticDataObjects` array. The `DatasetMappingHandler` processes each element as a separate dataset, allowing you to organize related entities like Customers and Orders within one model file.

### What is the difference between standard and shared datasets in Ossie?

Standard datasets use `table_type = "Standard"` and receive automatic default values from the `applyDefaults` method. Shared (non-standard) datasets do not receive these defaults and are stored in `custom_extensions` via the `storeSharedEntitiesInCustomExtensions` method to preserve their unique configurations during Salesforce conversion.

### Where should I store vendor-specific metadata that doesn't fit standard Ossie fields?

Place vendor-specific metadata in the **`custom_extensions`** field at the dataset level. The `CustomExtensionHandler` automatically manages these properties during bidirectional conversion, ensuring they persist when translating between Ossie and Salesforce formats without corrupting the standard schema.

### When should I use multiple entries in the semantic_model array?

Use multiple `semantic_model` entries only for multi-tenant deployments or scenarios requiring completely independent models to coexist. Most Ossie tools assume a single entry per the `ConverterConstants.SEMANTIC_MODEL` definition, so multiple entries introduce unnecessary complexity for standard single-tenant use cases.