Platform Manual
Data Types
Map
with conflict free replicated data type (crdt) technology, each document is represented as a map a map is useful when you want to create a list of items and update items over time within a document basic structure a map is a json like object that serves as the basis of each ditto document and is structured as a collection of field value pairs to represent simple values in a map , use any primitive data type, such as a string , boolean , number , and so on to represent a highly complex data structure in a map , use register , counter , array , or embed another map embedding a map within another map establishes an additional hierarchy the following snippet demonstrates a ditto document with an embedded map json { " id" "123546" "boolean" true, "string" "hello world", "number" 10, "map" { "key" "value", } } characteristics and behaviors following are the key attributes of the map type a map is represented in the document as a tree like structure that establishes a hierarchical, parent child relationship between the dataset in the document use maps in scenarios where you want to create a list of items and update that list over time if one peer creates a field within a document as an array and another peer an object, such as a map or register , the values do not merge embedding a map to create a single map represented as a json like root object in the document, use the following data model do { // insert json compatible data into ditto try ditto store\["foo"] upsert(\[ "boolean" true, "string" "hello world", "number" 10, "map" \["key" "value"], "array" \[1,2,3], "null" nil ]) } catch { //handle error print(error) }ditto store\["foo"] upsert(mapof( "boolean" to true, "string" to "hello world", "number" to 10, "map" to mapof("key" to "value"), "array" to listof(1,2,3), "null" to null ))// insert json compatible data into ditto await ditto store collection('people') upsert({ boolean true, string 'hello world', number 10, map { key 'value' }, array \[], null null, })// insert json compatible data into ditto map\<string, object> content = new hashmap<>(); content put("boolean", true); content put("string", "hello world"); content put("number", 10); map\<string, string> innermap = new hashmap<>(); innermap put("key", "value"); content put("map", innermap); content put("array", arrays aslist(1, 2, 3)); content put("null", null); ditto store collection("foo") upsert(content);var content = new dictionary\<string, object> { { "boolean", true }, { "string", "hello world" }, { "number", 10 }, { "map", new dictionary\<string, string>{{ "key", "value"}} }, { "array", new\[] {1, 2, 3} }, { "null", null } }; ditto store collection("foo") upsert(content);// insert json compatible data into ditto ditto get store() collection("foo") upsert(json({{"boolean", true}, {"string", "hello world"}, {"number", 10}, {"map", {{"key", "value"}}}, {"array", {1, 2, 3}}, {"null", null}}));collection upsert(json!({ "boolean" true, "string" "hello world", "number" 10, "map" { "key" "value" }, "array" \[1,2,3], "null" null, })) unwrap(); if you need to represent and organize data in a hierarchical structure, you can embed a map within another map to establish a parent child relationship within a document each document in ditto is inherently a map object at its root that is, when you use the upsert api to create a new document, a top level map ditto automatically generates a crdt map at the root of the document for instance, the parent field in the following snippet is actually the document's root map for more information, see platform manual > docid\ uti2pcpnojh74whsurd9r json { "parent" "susan", "map" { "child1" { "key1" "value1", "key2" "value2" }, "child2" { "key1" "value3", "key2" "value4" } } } updating a map when updating and adding fields to a map embedded within another map , use keypath indexing also referred to as dot syntax , a keypath index concisely specifies the fields to update when working with a map embedded within another map in a keypath index, each dot ( ) represents a level in the map hierarchy for example, friends foo indicates that the foo field is a child of the parent friends map , as demonstrated in the following snippet preferred set specific value by calling the remove method, as follows, you omit only the foo field from the friends map within the document, while the other fields within the friends map remain unaffected pseudocode collection findbyid("map test") update(doc => { doc at("friends foo") set("bar") }) not recommended update entire map the following snippet results in all of the values in the friends map being replaced with the new object ({ "beep" "boop" }) pseudocode collection findbyid("map test") update(doc => { doc at("friends") set({ "beep" "boop" }) }) removing a map since crdt map values merge with the existing document, simply omitting them from the crdt map does not remove them instead, the crdt map creates an operation for that field, and subsequently the existing fields remain unchanged https //docs ditto live/javascript/common/datamodel/map#remove preferred update specific value by calling the remove method, as follows, you omit only the foo field from the friends map within the document, while the other fields within the friends map remain unaffected pseudocode collection findbyid("map test") update(doc => { doc at("friends foo") set("bar") }) not recommended update entire map when you want to clear the entire map structure embedded in the document, call the set method for example, the following snippet illustrates the process of removing the friends map through the set method, making it empty pseudocode collection findbyid("map test") update(doc => { doc at("friends") set({ "beep" "boop" }) }) handling type level concurrency conflicts an issue unique to maps is the possibility for two offline peers to create a new document, in which one peer represents the field as an object ( map ), while the other peer represents the field as an array example scenario divergent types preventing merge the following snippets illustrate a scenario of a type level conflict unique to maps peer a creates the following new document json { "name" "bob jones", "address" { "street" "long road", "house number" 10298, "zip" "90210" } } while at the same time peer b creates the following new document json { "name" "bob jones", "address" \[ 10298, "long road", "90210" ] } because peer a and peer b use divergent data structures, combining an array with an object ( map ) is impossible rather than adhering to the default "last updated type" win principle, which can trigger a ping pong alteration of types between connected peers, as demonstrated in the following snippet, retain both values for the address field property by creating a data structure that accommodates both the object ( map ) and the array a ping pong alteration of types occurs when distinct peers repeatedly modify the data type of a specific field in response to each other's updates, leading to a forever loop of back‑and‑forth behavior { "name" "bob jones", "address" { "objectversion" { "street" "long road", "house number" 10298, "zip" "90210" }, "arrayversion" \[ 10298, "long road", "90210" ] } } preventing the ping pong effect to avoid the ping pong effect when conflicts between data types occur, retain both the array and the map object representations of the field in the document for example, in the previous scenario, you keep both versions of the conflicting representation of the address field in the document retaining both the array and map representations prevents back and forth, ping pong behavior ensures that there is no loss of data provides flexibility, allowing you to choose the data type that is most appropriate for encoding data in json based on your specific requirements and use case managing concurrency conflicts update history the best approach to handle conflicts that result from two peers making concurrent offline edits and then later rejoining online depends on your specific requirements and use case following is an overview of best approaches for handling concurrency conflicts resolving concurrency conflicts — if you want to give priority to the "latest" change, use a register ditto's register type use last write wins semantics so the value written last always becomes the current value auditing concurrency conflicts — if you want to keep track of the changes made by different peers over time, use the map type to model your list of operations each write operation is independently tracked as a field value pair, with the field representing the unique identifier and the value storing only the specific changes made by a given peer prompting end users to choose — if you want your end users to resolve concurrency conflicts instead of ditto, use the map type inside of your document and prompt end users to select the value to replicate example scenario using a map for concurrent updates imagine a scenario in which two ditto stores, peer a and peer b, have the following document json { " id" "abc123", "color" "red", "make" "toyota", "mileage" 160000, "inspections" "\<very large map>" } peer a calls the upsert method to change the field value color\ red to color\ blue val initialdocument = mapof( " id" to "abc123", "color" to "blue" )upsert({ id "abc123", color "blue" }) while at the same time peer b calls the update method to change the value of the mileage field findbyid("abc123") update(doc => { doc mileage incrememt(200) }) ditto store collection("cars") update(initialdocument)findbyid("abc123") update(doc => { doc mileage incrememt(200) }) when the changes replicate across the distributed peers, both changes merge resulting in both peer a and peer b ditto stores having the mileage increment of 200 and the color change to blue json { " id" "abc123", "color" "blue", "make" "toyota", "mileage" 160200, "smogreports" "\<very long json blob>" }