From 83e21ec44028fd48a6e7bb0376fa6c23668bdecb Mon Sep 17 00:00:00 2001 From: Cam <21029087+cxmeel@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:01:28 +0000 Subject: [PATCH] Implement `Dictionary.patchDiff` (closes #19) --- src/Dictionary/init.luau | 1 + src/Dictionary/patchDiff.luau | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/Dictionary/patchDiff.luau diff --git a/src/Dictionary/init.luau b/src/Dictionary/init.luau index d753778..abcbbf9 100644 --- a/src/Dictionary/init.luau +++ b/src/Dictionary/init.luau @@ -36,6 +36,7 @@ local dictionary = { map = require("./map"), merge = require("./merge"), mergeDeep = require("./mergeDeep"), + patchDiff = require("./patchDiff"), removeKey = require("./removeKey"), removeKeys = require("./removeKeys"), removeValue = require("./removeValue"), diff --git a/src/Dictionary/patchDiff.luau b/src/Dictionary/patchDiff.luau new file mode 100644 index 0000000..852d43d --- /dev/null +++ b/src/Dictionary/patchDiff.luau @@ -0,0 +1,88 @@ +export type Operation = "add" | "remove" | "replace" + +export type Patch = { + op: Operation, + path: { K }, + value: V, +} + +--[=[ + @within Dictionary + + Returns an array of patches that can be applied to `dictionary` to make it equal to `other`. This is a deep comparison. The patches are similar to those used in [JSON Patch](https://jsonpatch.com/). + + ```lua + local dictionary1 = { + foo = "bar", + qux = { + baz = "quux", + }, + } + + local dictionary2 = { + foo = "bar", + qux = { + baz = "quuz", + }, + baz = "quux", + } + + patchDiff(dictionary1, dictionary2) --[[ + { + { op = "replace", path = { "qux", "baz" }, value = "quuz" }, + { op = "add", path = { "baz" }, value = "quux" }, + } + ]] + ``` +]=] +local function patchDiff(dictionary: { [K]: V }, other: { [K]: V }): { Patch } + local out: { any } = {} + + for key, value in dictionary do + if other[key] == nil then + table.insert(out, { + op = "remove", + path = { key }, + value = value, + }) + + continue + end + + if typeof(value) == "table" then + local subpatches = patchDiff(value, other[key]) + + for _, patch in subpatches do + table.insert(out, { + op = patch.op :: any, + path = { key, table.unpack(patch.path) }, + value = patch.value, + }) + end + + continue + end + + if value ~= other[key] then + table.insert(out, { + op = "replace", + path = { key }, + value = other[key], + }) + end + end + + for key, value in other do + if dictionary[key] == nil then + table.insert(out, { + op = "add", + path = { key }, + value = value, + }) + end + end + + return out +end + +return patchDiff