Skip to content

Instantly share code, notes, and snippets.

@cajuncoding
Last active March 8, 2026 00:23
Show Gist options
  • Select an option

  • Save cajuncoding/bf78bdcf790782090d231590cbc2438f to your computer and use it in GitHub Desktop.

Select an option

Save cajuncoding/bf78bdcf790782090d231590cbc2438f to your computer and use it in GitHub Desktop.

Revisions

  1. cajuncoding revised this gist Mar 7, 2026. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,9 @@ public static class SystemTextJsonMergeExtensions
    /// NOTE: JsonNodes are context aware and track their parent relationships therefore to merge the values both JsonNode objects
    /// specified are mutated. The Base is mutated with new data while the source is mutated to remove reverences to all
    /// fields so that they can be added to the base.
    /// This is unfortunately an unavoidable behavior of System.Text.Json. Therefore, I've opted to keep the mutating
    /// behavior in place to optimize for performance as the default behavior. And, it can be easily resolved by the caller
    /// simply calling `jsonNode.DeepClone()` prior to the merge -- providing full control to the caller.
    ///
    /// Source taken directly from the open-source Gist here:
    /// https://gist.github.com/cajuncoding/bf78bdcf790782090d231590cbc2438f
  2. cajuncoding revised this gist Mar 4, 2026. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -81,6 +81,6 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge, bool me
    /// <param name="options"></param>
    /// <returns></returns>
    public static JsonNode MergeDictionary<TKey, TValue>(this JsonNode jsonBase, IDictionary<TKey, TValue> dictionary, JsonSerializerOptions options = null, bool mergeIfAlreadyExists = true)
    => jsonBase.Merge(JsonSerializer.SerializeToNode(dictionary, options));
    => jsonBase.Merge(dictionary.ToJsonNode(options), mergeIfAlreadyExists);
    }
    }
  3. cajuncoding revised this gist Jan 26, 2026. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge, bool me
    }
    default:
    throw new ArgumentException($"The JsonNode type [{jsonBase.GetType().Name}] is incompatible for merging with the target/base " +
    $"type [{jsonMerge.GetType().Name}]; merge requires the types to be the same.");
    $"type [{jsonMerge.GetType().Name}]; merge requires the types to be the same.");

    }

  4. cajuncoding revised this gist Jan 26, 2026. 1 changed file with 8 additions and 3 deletions.
    11 changes: 8 additions & 3 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,7 @@ public static class SystemTextJsonMergeExtensions
    /// <param name="jsonMerge"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentException"></exception>
    public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)
    public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge, bool mergeIfAlreadyExists = true)
    {
    if (jsonBase == null || jsonMerge == null)
    return jsonBase;
    @@ -39,6 +39,7 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)

    foreach (var prop in mergeNodesArray)
    {
    if(mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key))
    jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch
    {
    JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => jsonBaseChildObj.Merge(jsonMergeChildObj),
    @@ -54,7 +55,7 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)
    // so they can then be re-assigned to the target/base Json...
    var mergeNodesArray = jsonMergeArray.ToArray();
    jsonMergeArray.Clear();
    foreach(var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode);
    foreach (var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode);
    break;
    }
    default:
    @@ -68,14 +69,18 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)

    /// <summary>
    /// Merges the specified Dictionary of values into the base JsonNode for which this method is called.
    ///
    /// Source taken directly from the open-source Gist here:
    /// https://gist.github.com/cajuncoding/bf78bdcf790782090d231590cbc2438f
    ///
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="jsonBase"></param>
    /// <param name="dictionary"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static JsonNode MergeDictionary<TKey, TValue>(this JsonNode jsonBase, IDictionary<TKey, TValue> dictionary, JsonSerializerOptions options = null)
    public static JsonNode MergeDictionary<TKey, TValue>(this JsonNode jsonBase, IDictionary<TKey, TValue> dictionary, JsonSerializerOptions options = null, bool mergeIfAlreadyExists = true)
    => jsonBase.Merge(JsonSerializer.SerializeToNode(dictionary, options));
    }
    }
  5. cajuncoding revised this gist Jul 12, 2024. 1 changed file with 6 additions and 4 deletions.
    10 changes: 6 additions & 4 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -39,10 +39,12 @@ public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)

    foreach (var prop in mergeNodesArray)
    {
    if (jsonBaseObj[prop.Key] is JsonObject jsonBaseChildObj && prop.Value is JsonObject jsonMergeChildObj)
    jsonBaseObj[prop.Key] = jsonBaseChildObj.Merge(jsonMergeChildObj);
    else
    jsonBaseObj[prop.Key] = prop.Value;
    jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch
    {
    JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj => jsonBaseChildObj.Merge(jsonMergeChildObj),
    JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray => jsonBaseChildArray.Merge(jsonMergeChildArray),
    _ => prop.Value
    };
    }
    break;
    }
  6. cajuncoding revised this gist Jun 3, 2024. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -4,9 +4,9 @@
    using System.Text.Json;
    using System.Text.Json.Nodes;

    namespace Maestro.Common.CustomJsonSerialization
    namespace CajunCoding
    {
    public static class SystemTextJsonExtensions
    public static class SystemTextJsonMergeExtensions
    {
    /// <summary>
    /// Merges the specified Json Node into the base JsonNode for which this method is called.
  7. cajuncoding revised this gist Jun 3, 2024. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,10 @@ public static class SystemTextJsonExtensions
    /// NOTE: JsonNodes are context aware and track their parent relationships therefore to merge the values both JsonNode objects
    /// specified are mutated. The Base is mutated with new data while the source is mutated to remove reverences to all
    /// fields so that they can be added to the base.
    ///
    /// Source taken directly from the open-source Gist here:
    /// https://gist.github.com/cajuncoding/bf78bdcf790782090d231590cbc2438f
    ///
    /// </summary>
    /// <param name="jsonBase"></param>
    /// <param name="jsonMerge"></param>
  8. cajuncoding revised this gist Jun 3, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@ public static class SystemTextJsonExtensions
    {
    /// <summary>
    /// Merges the specified Json Node into the base JsonNode for which this method is called.
    /// It is null safe and can be easily used with null-check & null coalesce operators for fluent calls.
    /// NOTE: JsonNodes are context aware and track their parent relationships therefore to merge the values both JsonNode objects
    /// specified are mutated. The Base is mutated with new data while the source is mutated to remove reverences to all
    /// fields so that they can be added to the base.
  9. cajuncoding created this gist Jun 3, 2024.
    74 changes: 74 additions & 0 deletions SystemTextJsonNodeMergeExtensions.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.Json;
    using System.Text.Json.Nodes;

    namespace Maestro.Common.CustomJsonSerialization
    {
    public static class SystemTextJsonExtensions
    {
    /// <summary>
    /// Merges the specified Json Node into the base JsonNode for which this method is called.
    /// NOTE: JsonNodes are context aware and track their parent relationships therefore to merge the values both JsonNode objects
    /// specified are mutated. The Base is mutated with new data while the source is mutated to remove reverences to all
    /// fields so that they can be added to the base.
    /// </summary>
    /// <param name="jsonBase"></param>
    /// <param name="jsonMerge"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentException"></exception>
    public static JsonNode Merge(this JsonNode jsonBase, JsonNode jsonMerge)
    {
    if (jsonBase == null || jsonMerge == null)
    return jsonBase;

    switch (jsonBase)
    {
    case JsonObject jsonBaseObj when jsonMerge is JsonObject jsonMergeObj:
    {
    //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array so the node can then be
    // re-assigned to the target/base Json; clearing the Object seems to be the most efficient approach...
    var mergeNodesArray = jsonMergeObj.ToArray();
    jsonMergeObj.Clear();

    foreach (var prop in mergeNodesArray)
    {
    if (jsonBaseObj[prop.Key] is JsonObject jsonBaseChildObj && prop.Value is JsonObject jsonMergeChildObj)
    jsonBaseObj[prop.Key] = jsonBaseChildObj.Merge(jsonMergeChildObj);
    else
    jsonBaseObj[prop.Key] = prop.Value;
    }
    break;
    }
    case JsonArray jsonBaseArray when jsonMerge is JsonArray jsonMergeArray:
    {
    //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array,
    // so they can then be re-assigned to the target/base Json...
    var mergeNodesArray = jsonMergeArray.ToArray();
    jsonMergeArray.Clear();
    foreach(var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode);
    break;
    }
    default:
    throw new ArgumentException($"The JsonNode type [{jsonBase.GetType().Name}] is incompatible for merging with the target/base " +
    $"type [{jsonMerge.GetType().Name}]; merge requires the types to be the same.");

    }

    return jsonBase;
    }

    /// <summary>
    /// Merges the specified Dictionary of values into the base JsonNode for which this method is called.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="jsonBase"></param>
    /// <param name="dictionary"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static JsonNode MergeDictionary<TKey, TValue>(this JsonNode jsonBase, IDictionary<TKey, TValue> dictionary, JsonSerializerOptions options = null)
    => jsonBase.Merge(JsonSerializer.SerializeToNode(dictionary, options));
    }
    }