Skip to content

Conversation

@Valian
Copy link
Contributor

@Valian Valian commented Aug 20, 2025

@corka149 I think it's the last PR I'm submitting.

The issue I encountered was with changing type of maps via prepare_map - eg Date or DateTime to strings. This bugfix should fix it, and also made a small refactor to simplify some parts of the code (mostly escaping of paths).

I would greatly appreciate if you could release a new version. Immediately after I'll use it as a dependency in my project 🤗


  • Replaced inline function for prepare_map with Function.identity/1 for better performance.
  • Consolidated logic in do_diff to handle type changes and map preparation more efficiently.
  • Updated path joining to use join_key/2 for handling nil values.
  • Added a test case for handling type changes in map values.

- Replaced inline function for `prepare_map` with `Function.identity/1` for better performance.
- Consolidated logic in `do_diff` to handle type changes and map preparation more efficiently.
- Updated path joining to use `join_key/2` for handling nil values.
- Added a test case for handling type changes in map values.
@corka149 corka149 requested review from Copilot and corka149 August 24, 2025 14:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug where the prepare_map function returning non-map values didn't work correctly when diffing maps, and includes performance and code clarity improvements.

  • Fixed handling of type changes when prepare_map transforms map values to non-map types
  • Replaced inline anonymous function with Function.identity/1 for better performance
  • Consolidated path building logic with a new join_key/2 helper function

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
lib/jsonpatch.ex Fixed prepare_map type change handling, consolidated diff logic, and added path building helper
test/jsonpatch_test.exs Added test case for prepare_map transforming Date structs to strings

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 221 to 222
defp do_diff(dest, source, path, key, patches, opts) when are_unequal_lists(dest, source) do
# uneqal lists, let's use a specialized function for that
Copy link

Copilot AI Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix typo in comment: 'uneqal' should be 'unequal'.

Suggested change
defp do_diff(dest, source, path, key, patches, opts) when are_unequal_lists(dest, source) do
# uneqal lists, let's use a specialized function for that
# unequal lists, let's use a specialized function for that

Copilot uses AI. Check for mistakes.

This comment was marked as outdated.

@corka149 corka149 requested a review from Copilot August 24, 2025 14:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug where the prepare_map function could return non-map values, causing type changes to not be handled correctly during diff generation. It also includes performance improvements and code refactoring.

  • Replaced inline prepare_map function with Function.identity/1 for better performance
  • Consolidated diff logic to handle type changes after map preparation more efficiently
  • Refactored path joining logic using a new join_key/2 helper function

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
lib/jsonpatch.ex Core bug fix and refactoring - handles type changes from prepare_map, consolidates diff logic, and adds join_key/2 helper
test/jsonpatch_test.exs Adds test case demonstrating the fix for type changes via prepare_map (Date to string conversion)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +285 to +294
defp do_list_diff(destination, source, ancestor_path, patches, opts) do
if opts[:object_hash] do
do_hash_list_diff(destination, source, ancestor_path, patches, opts)
else
do_pairwise_list_diff(destination, source, ancestor_path, patches, idx, opts)
do_pairwise_list_diff(destination, source, ancestor_path, patches, 0, opts)
end
catch
# happens if we've got a nil hash or we tried to hash a non-map
:hash_not_implemented ->
do_pairwise_list_diff(destination, source, ancestor_path, patches, idx, opts)
do_pairwise_list_diff(destination, source, ancestor_path, patches, 0, opts)
Copy link

Copilot AI Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature has changed to remove the idx parameter, but the hardcoded 0 values in lines 288 and 293 suggest this parameter is still needed. Consider adding the idx parameter back or ensuring the called functions can handle starting from index 0 correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +215 to 245
do_diff(destination, source, opts[:ancestor_path], nil, [], opts)
end

defguardp are_unequal_maps(val1, val2) when val1 != val2 and is_map(val2) and is_map(val1)
defguardp are_unequal_lists(val1, val2) when val1 != val2 and is_list(val2) and is_list(val1)

defp do_diff(dest, source, path, key, patches, opts) when are_unequal_lists(dest, source) do
# uneqal lists, let's use a specialized function for that
do_list_diff(dest, source, "#{path}/#{escape(key)}", patches, 0, opts)
do_list_diff(dest, source, join_key(path, key), patches, opts)
end

defp do_diff(dest, source, path, key, patches, opts) when are_unequal_maps(dest, source) do
# uneqal maps, let's use a specialized function for that
do_map_diff(dest, source, "#{path}/#{escape(key)}", patches, opts)
# Convert structs to maps if prepare_map function is provided
dest = maybe_prepare_map(dest, opts)
source = maybe_prepare_map(source, opts)

if not is_map(dest) or not is_map(source) do
# type changed, let's process it again
do_diff(dest, source, path, key, patches, opts)
else
# uneqal maps, let's use a specialized function for that
do_map_diff(dest, source, join_key(path, key), patches, opts)
end
end

defp do_diff(dest, source, path, key, patches, opts) when dest != source do
# scalar values or change of type (map -> list etc), let's just make a replace patch
value = maybe_prepare_map(dest, opts)
[%{op: "replace", path: "#{path}/#{escape(key)}", value: value} | patches]
[%{op: "replace", path: join_key(path, key), value: maybe_prepare_map(dest, opts)} | patches]
end

defp do_diff(_dest, _source, _path, _key, patches, _opts) do
Copy link

Copilot AI Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recursive call to do_diff could potentially cause infinite recursion if prepare_map consistently returns non-map values. Consider adding a guard or depth limit to prevent stack overflow.

Copilot uses AI. Check for mistakes.
@corka149 corka149 merged commit 3977552 into corka149:master Aug 24, 2025
10 checks passed
@corka149
Copy link
Owner

Hey @Valian . 2.3.1 was released. :-)

@Valian
Copy link
Contributor Author

Valian commented Aug 24, 2025

Amazing! Thank you :)

PS. Tags for 2.3.0 and 2.3.1 seem to be missing on GH, that's why I missed that 2.3.0 was released.

@corka149
Copy link
Owner

Hups, thx for the hint. I fixed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants