Browse Source

deepCopyOnMap

Tries to deeply map if value exists.
Herby Vojčík 5 years ago
parent
commit
c68db5f9fd
2 changed files with 100 additions and 0 deletions
  1. 78 0
      README.md
  2. 22 0
      src/cow-value-model.js

+ 78 - 0
README.md

@@ -261,6 +261,84 @@ an object will be treated as a `Map` (unlike the default string case,
 where it is treated as an object), so copy wil be created
 using `new Map(obj)`, not using `{...obj}`.
 
+### `deepCopyOnMap(key, ...)(fn)`
+### `decomap(key, ...)(fn)`
+
+Creates a modifier function allowing
+to "map" value at specified key
+in an immutable fashion, eg. creating a modified copy
+when actual modification happens.
+
+If properties that are to be parents of a sub-value
+are not present, they are _not_ created.
+In other words, if the key to modify does not exist,
+no change happens.
+
+Specify keys by passing a list of keys to `deepCopyOnMap`.
+Key can be either:
+ - number
+ - `keyInMap(obj)`
+ - array of Keys
+ - anything else, which is `toString()`ed and dot-split.
+
+In case no change actually happens (same value is set which is
+already present), returns the original object.
+
+```js
+const mapName = decomap("name");
+  
+mapName(x => x.toUpperCase())({name: "Tom"});
+// => {name: "TOM"}
+  
+const mapCity = decomap("address.city");
+const mapCity2 = decomap("address", "city");
+// and other forms, like:
+// const mapCity3 = decomap(["address", "city"]);
+// const mapCity4 = decomap("address", [[], "city"]);
+// const mapCity5 = decomap([[], "address.city"]);
+// etc.
+const object = {address: {city: "New York"}};
+  
+mapCity(x => "London")(object);
+// => {address: {city: "London"}}
+mapCity2(x => "London")(object);
+// => {address: {city: "London"}}
+object;
+// => {address: {city: "New York"}}
+mapCity(x => "New York")(object) === object;
+// => true
+mapCity2(x => "New York")(object) === object;
+// => true
+  
+const mapCityLondon = mapCity(x => "London");
+  
+mapCityLondon(undefined);
+// => undefined
+mapCityLondon(null);
+// => null
+mapCityLondon({});
+// => {}
+mapCityLondon({address: null});
+// => {address: null}
+mapCityLondon({address: {}});
+// => {address: {}}
+mapCityLondon({address: {city: null}});
+// => {address: {city: "London"}}
+```
+
+If you put a number in a list of keys to use,
+an object will be treated as an array (unlike the default string case,
+where it is treated as an object), so copy wil be created
+using `[...obj]`, not using `{...obj}`.
+
+That way you can create eg. `const c = decomap("person", 34, "name")`
+to "set" `obj.person[34].name` with `c(fn)(obj)`.
+
+If you put a `keyInMap(key)` in a list of keys to use,
+an object will be treated as a `Map` (unlike the default string case,
+where it is treated as an object), so copy wil be created
+using `new Map(obj)`, not using `{...obj}`.
+
 ### `keyInMap(obj)`
 ### `kim(obj)`
 

+ 22 - 0
src/cow-value-model.js

@@ -45,6 +45,11 @@ const constructKeys = function (keyDescriptions) {
 
 const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key];
 
+const getKeyWithCheck = (x, key) =>
+    x == null ? null :
+        isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null :
+            x.hasOwnProperty(key) ? {value: x[key]} : null;
+
 export const deepGetOrNil = (...keyDescriptions) => {
     const keys = constructKeys(keyDescriptions);
     return obj => keys.reduce(getKey, obj);
@@ -68,6 +73,23 @@ export const deepCopyOnWrite = (...keyDescriptions) => {
 
 export const decow = deepCopyOnWrite;
 
+export const deepCopyOnMap = (...keyDescriptions) => {
+    const keys = constructKeys(keyDescriptions);
+
+    function mapFn (x, index, fn) {
+        if (index >= keys.length) return fn(x);
+        const key = keys[index],
+            value = getKeyWithCheck(x, key);
+        if (value == null) return x;
+        const modified = mapFn(value.value, index + 1, fn);
+        return value.value === modified ? x : copyWith(x, key, modified);
+    }
+
+    return fn => obj => mapFn(obj, 0, fn);
+};
+
+export const decomap = deepCopyOnMap;
+
 export const cowValueModel = (...keyDescriptions) => {
     const GET_SENTINEL = {};