---
title: MAP
slug: v4-5/data-types/map
docTags: 
createdAt: 2023-10-05T15:11:08.674Z
---

An alternative approach to using a JSON object as a `REGISTER` to represent and organize data in a hierarchical structure within a document is to use a `MAP`.

Similar to a JSON object, the `MAP` data type in DQL forms a tree-like parent-child relationship within a document's dataset.

However, unlike a JSON object which is limited to scalar subtypes, functions as a single value, and enforces a last-write-wins merge strategy, a `MAP` allows:

- Nesting of various DQL data types:`REGISTER`, `MAP`, and `ATTACHMENT`

- Independent modification of each key-value pair

- Utilization of an *add-wins* merge strategy for conflict resolution. With the add-wins approach, all conflicting changes are retained rather than converging on only one

# MAP Data Structure

The response structure of a `MAP` is a JSON-like object; however, each field in a `MAP` has a corresponding data type. This means that each field within a `MAP` has an independent merge definition the same as fields on the top-level document.

The `MAP` type is useful when you want to create a list of non-conflicting items and update items over time within a document.&#x20;

The following snippet demonstrates a Ditto document with an embedded `MAP` `my_map`:

:::CodeblockTabs
Ditto Document

```json
{
  "_id": "123"
  "my_map": {
    "sub_field": "value",
  }
}
```
:::

# Add-Wins Merge Strategy

The `MAP` type uses the add-wins approach to resolving concurrency conflicts.&#x20;

So instead of choosing a single definitive value to merge and distribute across peers like the last-write-wins strategy utilized by the `REGISTER` and `ATTACHMENT` data types, choose them all  — even if the data is already contained within the `MAP` object.&#x20;

# Declaring a MAP

The `REGISTER` is the default data type in Ditto; therefore, there is no need for explicit declaration — it's implicitly assumed. &#x20;

However, to use a `MAP` (and `ATTACHMENT`) data type, you must explicitly declare it within your DQL statement using *Type Definition&#x20;*&#x73;yntax. A Type Definition is a description that informs Ditto that the given set of key-value pairs embedded in the document as a nested structure is of the non-`REGISTER` type and has specific characteristics in how it handles data.&#x20;

To declare a data type a `MAP`, you use the `COLLECTION` identifier prefix, followed by the collection name enclosed in parentheses, along with a type definition. All done inline as follows:&#x20;

:::CodeblockTabs
DQL

```sql
... COLLECTION your_collection_name (my_map_1 MAP, my_map_2 MAP, ...) ...
```
:::

For more information and how-to instructions, see [Ditto Query Language](docId\:e3UQqeqVzvOTjlS2xfDFl) > [Types and Definitions](docId\:GsuSiC4zSrjq_0h07Ckn_).

# Creating a MAP in a Document

There are two ways to create a `MAP` structure:

- In an `INSERT` operation — Create a new document and nest it with this set of fields. (See [Inserting to Create a MAP](./#inserting-to-create-a-map))

- In an `UPDATE` operation — If the `MAP` does not already exist, create it. (See [Updating to Create a MAP](./#updating-to-create-a-map))

When you need to represent a highly-complex data structure in a `MAP`, consider embedding another `MAP`. Embedding a `MAP` within a `MAP` establishes an additional hierarchy. There are The decision to use deeply embedded `MAPS` in a single document or opt for a *flat model* instead depends on your specific requirements, relationships between data, and tolerance for certain tradeoffs.&#x20;

:::hint{type="info"}
The flat model is a simple, non‑embedded structure in which you spread your data across multiple separate documents.&#x20;
:::

## Inserting to Create a MAP

When inserting a new document in Ditto, you can define a field as a `MAP` and include the structure of key-value pairs nested within it — a two-in-one approach. For example:

:::CodeblockTabs
```swift
let arguments: [String: Any] = [
  "newCar": [
    "_id": "123",
    "properties": [
      "color": "blue",
      "mileage": 3000
    ]
  }
];

await ditto.store.execute(
  query: "INSERT INTO COLLECTION cars (properties MAP) DOCUMENTS (:newCar)",
  arguments: arguments);
```

```kotlin
val arguments = mapOf(
  "newCar" to mapOf(
    "_id" to "123",
    "properties" to mapOf(
      "color" to "blue",
      "mileage" to 3000
      )
    )
  )
)

ditto.store.execute("""
  INSERT INTO COLLECTION cars (properties MAP)
  DOCUMENTS (:newCar)
  """,
  arguments)
```

```javascript
const newCar = {
  _id: "123",
  properties: {
    color: 'blue',
    mileage: 3000
  }
}

await ditto.store.execute(`
  INSERT INTO COLLECTION cars (properties MAP)
  DOCUMENTS (:newCar)`,
  { newCar });
```

```java
Map<String, Object> properties = new HashMap<>();
properties.put("color", "blue");
properties.put("mileage", 3000);

Map<String, Object> newCar = new HashMap<>();
newCar.put("_id", "123");
newCar.put("properties", properties);

DittoQueryResult result = (DittoQueryResult) ditto.store.execute(
    "INSERT INTO COLLECTION cars (properties MAP) DOCUMENTS (:newCar)",
    Collections.singletonMap("newCar", newCar),
    new Continuation<>() {
        @NonNull
        @Override
        public CoroutineContext getContext() {
            return EmptyCoroutineContext.INSTANCE;
        }

        @Override
        public void resumeWith(@NonNull Object o) {
            if (o instanceof Result.Failure) {
                // Handle failure
            }
        }
    }
);
```

```csharp
var subMap = new {
  color = "blue",
  mileage = 3000
};
var insertArgs = new Dictionary<string, object>();
insertArgs.Add("newCar", new { _id = newId, properties = subMap })

var result = await ditto.Store.ExecuteAsync(
  "INSERT INTO COLLECTION cars (properties MAP) DOCUMENTS (:newCar)",
  insertArgs);
```

```cpp
struct Properties {
  std::string color;
  int mileage;
};
struct Car {
  std::string _id;
  Properties properties;
};

// ...

std::map<std::string, Car> args;
args["newCar"] = {"123", {"blue", 3000}};

ditto.get_store().execute(
  "INSERT INTO COLLECTION your_collection_name (properties MAP)"
+ " DOCUMENTS (:newCar)",
  args).get();
```

```rust
struct Args {
  newCar: Car,
}
struct Properties {
  color: String,
  mileage: i32
}
struct Car {
  _id: String,
  properties: Properties
}

// ...

let args = Args {
  newCar: Car {
    _id: "123".to_string(),
    properties: Properties {
      color: "blue".to_string(),
      mileage: 3000
    }
  },
};

await ditto.store().execute(
  "INSERT INTO COLLECTION cars (properties MAP) DOCUMENTS (:newCar)",
  args);
```
:::

Another method to creating a `MAP` within a document is to perform an `UPDATE` operation. &#x20;

In the `UPDATE` approach, you set the structure of key-value pairs and specify the document ID for storage.&#x20;

For example, this statement creates the `MAP` structure — unless it already exists — setting a single key-value pair of color is red within the properties `MAP` for the document with the ID 123 in the cars collection:

:::CodeblockTabs
DQL

```sql
UPDATE COLLECTION cars (properties MAP)
SET
  properties -> (
    color = 'red'
  )
WHERE
  _id = '123' 
```
:::

For more information and how-to instructions, see [Ditto Query Language](docId\:e3UQqeqVzvOTjlS2xfDFl) > [CREATE](docId\:QsOasGYrr0l0DYdXy77cA).

## Updating to Create a Map

`MAPS` can be updated with specific DQL arrow operator syntax `-> (...)`. This syntax allows for multiple child fields to be edited on a single `MAP`.

:::CodeblockTabs
DQL

```sql
UPDATE COLLECTION cars (properties MAP)
SET
  properties -> (
    color = 'red',
    mileage = 3001
  )
WHERE
  _id = '123' 
```
:::

For more information and how-to instructions, see [Ditto Query Language](docId\:e3UQqeqVzvOTjlS2xfDFl) > [Types and Definitions](docId\:GsuSiC4zSrjq_0h07Ckn_) > [MAP Operations](docId\:GsuSiC4zSrjq_0h07Ckn_).

## Embedding a MAP in a MAP

If you need to represent and organize highly complex data in a hierarchical structure, consider embedding a `MAP` within another `MAP` to establish a parent-child relationship within a document as follows:

:::CodeblockTabs
```swift
let arguments: [String: Any] = [
  "newDocument": [
    "_id": "123",
    "top_map": [
      "nested_map": [
        "color": "blue"
      ]
    ]
  }
];

await ditto.store.execute(
  query: """
  INSERT INTO COLLECTION your_collection_name (top_map MAP(nested_map MAP))
  DOCUMENTS (:newDocument)
  """,
  arguments: arguments);
```

```kotlin
val arguments = mapOf(
  "newDocument" to mapOf(
    "_id" to "123",
    "top_map" to mapOf(
      "nested_map" to mapOf(
        "color" to "blue"
      )
    )
  )
)

ditto.store.execute("""
  INSERT INTO COLLECTION your_collection_name (top_map MAP(nested_map MAP))
  DOCUMENTS (:newDocument)
  """,
  arguments)
```

```javascript
const newDocument = {
  _id: "123",
  top_map: {
    nested_map: {
      color: "blue"
    }
  }
}

await ditto.store.execute(`
  INSERT INTO COLLECTION your_collection_name (top_map MAP(nested_map MAP))
  DOCUMENTS (:newDocument)`,
  { newDocument });
```

```java
Map<String, Object> nestedMap = new HashMap<>();
nestedMap.put("color", "blue");

Map<String, Object> topMap = new HashMap<>();
topMap.put("nestedMap", nestedMap);

Map<String, Object> newDocument = new HashMap<>();
newDocument.put("_id", "123");
newDocument.put("topMap", topMap);

DittoQueryResult result = (DittoQueryResult) ditto.store.execute(
    "INSERT INTO COLLECTION your_collection_name (top_map MAP(nested_mapMAP)) DOCUMENTS (:newDocument)",
    Collections.singletonMap("newDocument", newDocument),
    new Continuation<>() {
        @NonNull
        @Override
        public CoroutineContext getContext() {
            return EmptyCoroutineContext.INSTANCE;
        }

        @Override
        public void resumeWith(@NonNull Object o) {
            if (o instanceof Result.Failure) {
                // Handle failure
            }
        }
    }
);
```

```csharp
var nestedMap = new {
  color = "blue"
};
var topMap = new {
  nestedMap = nestedMap
};
var args = new Dictionary<string, object>();
args.Add("newDocument", new { _id = newId, topMap = topMap })

var result = await ditto.Store.ExecuteAsync(
  "INSERT INTO COLLECTION your_collection_name (top_map MAP(nested_map MAP))"
+ " DOCUMENTS (:newDocument)",
  args);
```

```cpp
struct NestedMap {
  std::string color;
};
struct TopMap {
  NestedMap nestedMap;
};
struct Document {
  std::string _id;
  TopMap topMap;
};

// ...

std::map<std::string, Document> args;
args["newDocument"] = {"123", TopMap{NestedMap{"blue"}}};

ditto.get_store().execute(
  "INSERT INTO COLLECTION your_collection_name (properties MAP)"
+ " DOCUMENTS (:newDocument)",
  args);
```

```rust
struct Args {
  newDocument: Document,
}
struct NestedMap {
  color: String
}
struct TopMap {
  nestedMap: NestedMap
}
struct Document {
  _id: String,
  topMap: TopMap
}

// ...

let args = Args {
  newDocument: Document {
    _id: "123".to_string(),
    topMap: TopMap {
      nestedMap: NestedMap {
        sobField: "blue".to_string()
      }
    }
  },
};

await ditto.store().execute("
  "INSERT INTO COLLECTION your_collection_name (properties MAP)"
+ " DOCUMENTS (:newDocument)",
  args);
```
:::

# Deleting a Map

The `MAP` data type can be marked as deleted using the `tombstone()` functional operation.

When a `MAP` is tombstoned, a metadata property is internally set on the field and all the data within the `MAP` is removed. This is done by iterating through all the fields in the `MAP` and calling `tombstone` on those fields. For `REGISTER` or `ATTACHMENT` data types within the `MAP` this will result in their values also being deleted.

:::CodeblockTabs
DQL

```sql
UPDATE COLLECTION cars (properties MAP)
SET
  properties -> tombstone()
WHERE
  _id = '123' 
```
:::

For more information and how-to instructions, see [Ditto Query Language](docId\:e3UQqeqVzvOTjlS2xfDFl) > [Types and Definitions](docId\:GsuSiC4zSrjq_0h07Ckn_) > [Tombstone Operation](docId\:GsuSiC4zSrjq_0h07Ckn_).

***

# Merge Strategy with MAP

The `MAP` type has an add-wins merge behavior. This means that the adding or updating of a field always wins over the removal (`tombstone`) of the same field when performing a merge across two peers.&#x20;

## Conflicts with Multiple Field Entries

If multiple fields are being added across Small Peer devices, the result will be a `MAP` with all added fields. Because each field in a `MAP` has its own data type, the merge strategy for that field will depend on the data type of that field.&#x20;

In the following example, USER 1 inserts `item1` and USER 2 inserts `item2` to an `items` `MAP`:

:::CodeblockTabs
DQL

```sql
# USER 1 adds item1
UPDATE COLLECTION my_collection (items MAP)
SET
  items -> (item1 = 'red')
WHERE
  _id = '123' 
```
:::

:::CodeblockTabs
DQL

```sql
# USER 2 adds item2
UPDATE COLLECTION my_collection (items MAP)
SET
  items -> (item2 = 'blue')
WHERE
  _id = '123' 
```
:::

The resulting document contains an `items` `MAP` with both `item1` and `item2:`

:::CodeblockTabs
Ditto Document

```json
{
  "_id": "123"
  "items": {
    "item1": "red",
    "item2": "blue",
  }
}
```
:::

## Comparison With REGISTER

The nested JSON object, functioning as a `REGISTER`, closely resembles a nested `MAP` structure within a document. However, their merge strategies and supported types for nested values differ significantly.

Understanding these distinctions is crucial when selecting the appropriate data type for expressing your data structure.

For example, consider a scenario in which you have a document with a `properties` field of the `REGISTER` data type storing information about a car in an embedded JSON object. You want to change only the `color` field for a car identified by document ID `123` to `blue`.

Since a `REGISTER` acts as a single object, to update just the `color`, you'd need to update the entire object by providing the entire set of nested key-value pairs, including the modified `color` , as follows:

:::CodeblockTabs
```swift
let newProperties = [
  "color": "blue",
  "sunroof": true,
  "door_count": 4
]
  
await ditto.store.execute(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",
  ["newProperties": newProperties]);
```

```kotlin
var result = ditto.store.execute(
  "INSERT INTO cars DOCUMENTS (:newCar)",
  mapOf("newProperties"
    to mapOf("color" to "blue", "sunroof" to true, "door_count" to 4)))
```

```javascript
const newProperties = {
  color: 'blue',
  sunroof: true,
  door_count: 4
}
  
await ditto.store.execute(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",     
  {newProperties});
```

```java
Map<String, String> newProperties = new HashMap<>();
newCar.put("color", "blue");
newCar.put("sunroof", true);
newCar.put("door_count", 4);

DittoQueryResult result = (DittoQueryResult) ditto.store.execute(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",
  Collections.singletonMap("newProperties", newProperties),
  new Continuation<>() {
    @NonNull
    @Override
    public CoroutineContext getContext() {
      return EmptyCoroutineContext.INSTANCE;
    }

    @Override
    public void resumeWith(@NonNull Object o) {
      if (o instanceof Result.Failure) {
        // Handle failure
      }
    }
  }
);

```

```csharp
var args = new Dictionary<string, object>();
args.Add("newProperties", new { color="blue", sunroof=true, doorCount=4});

await ditto.Store.ExecuteAsync(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",
  args);
```

```cpp
struct Properties {
  std::string color;
  bool sunroof;
  int door_count;
};

// ...

std::map<std::string, Properties> args;
args["newProperties"] = {"blue", true, 4};

ditto.get_store().execute(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",
  args);
```

```rust
struct Properties {
  color: String,
  sunroof: bool,
  door_count: i32
}

struct Args {
  newProperties: Properties,
}

// ...

let args = Args {
  newProperties: Properties {
    color: "blue".to_string(),
    sunroof: true,
    door_count: 4
  }
};

await ditto.store().execute(
  "UPDATE cars SET properties = :newProperties WHERE _id = '123'",
  args);
```
:::

However, if the `properties` field was of the `MAP` data type instead, to update just the color without altering the other nested key-value pairs in the set, you would simply set the `properties.color` field to `blue` as follows:

:::CodeblockTabs
DQL

```sql
UPDATE COLLECTION cars (properties MAP)
SET properties -> (color = blue)
WHERE _id = '123'
```
:::

## Conflicts with Tombstone and Modify

If a tombstone and modification (or `INSERT`) occur at the same time across two peers, the result is dependent on the casual order of changes.

:::hint{type="warning"}
Modifications to the value of fields within a `MAP` do not count as modifications to the `MAP` itself.&#x20;
:::

Following is a truth table showing "if a `MAP` is deleted" based on "if the peers are connected" and "when the tombstone operation occurs relative to the modification."&#x20;

| **PEER Status** | **Tombstone First** | **Tombstone Last** |
| --------------- | ------------------- | ------------------ |
| Connected       | FALSE               | TRUE               |
| Disconnected    | FALSE               | FALSE              |

# Warnings and Cautions

:::hint{type="danger"}
Syncing large documents can significantly impact sync performance.&#x20;

Caution is advised when handling a deeply embedded document or a very large document. If you have a deeply embedded or large document object, consider using an `ATTACHMENT` instead of a regular document.&#x20;
:::

:::hint{type="warning"}
When internet connection is limited or when using only Bluetooth to sync across connected peers, the process of syncing documents that contain large amounts of data is slow. For instance, when using only Bluetooth Low Energy (LE) to sync a document of a typical size, the rate of replication is 20 KB per second maximum. Given this, a document of 250 KB or larger may require 10 seconds or more to sync for the first time between Small Peer devices.

A slow replication rate results in a loading spinner being displayed to your end users until the sync process fully completes. This is due to the callback being unable to render the returned data; the end-to-end sync process requires that documents be broken down into smaller parts before being synced across the mesh, and then, until the client receives all of the smaller parts and reconstructs them, the document is not returned.

Instead of using a single document to encode all of your large dataset, use a series of smaller documents.

Note that if a document exceeds 250 KB in size, a `stdout` warning prints, and any documents larger than 5 MB will not sync to other peers.
:::

