< Summary

Information
Class: Chronicler.SerializationPayloadEditor
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/SerializationPayloadEditor.cs
Line coverage
81%
Covered lines: 60
Uncovered lines: 14
Coverable lines: 74
Total lines: 305
Line coverage: 81%
Branch coverage
80%
Covered branches: 37
Total branches: 46
Branch coverage: 80.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
SerializeRecord(...)0%620%
PopulateRecord(...)0%620%
RemovePayloadEntry(...)0%620%
SetPayloadValue(...)0%620%
.cctor()100%11100%
RemoveJsonProperty(...)100%44100%
SetJsonValue(...)100%44100%
ParseJsonRoot(...)100%44100%
GetJsonParent(...)100%44100%
RemoveMemoryPackEntry(...)100%44100%
SetMemoryPackValue(...)100%44100%
ReadEnvelope(...)50%22100%
RemoveMemoryPackEntry(...)100%66100%
SetMemoryPackValue(...)100%66100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Serialization/SerializationPayloadEditor.cs

#LineLine coverage
 1#if !CHRONICLER_DISABLE_MEMORYPACK
 2using MemoryPack;
 3#endif
 4using System;
 5using System.Text.Json;
 6using System.Text.Json.Nodes;
 7
 8namespace Chronicler;
 9
 10/// <summary>
 11/// Provides methods for editing serialized payloads without fully deserializing them.
 12/// </summary>
 13public static class SerializationPayloadEditor
 14{
 15    #region Common
 16
 17#if CHRONICLER_DISABLE_MEMORYPACK
 18
 19    /// <summary>
 20    /// Serializes a record to a payload format (JSON string or MemoryPack byte array) based on the specified options.
 21    /// </summary>
 22    /// <param name="record">The record to serialize.</param>
 23    /// <returns>The serialized payload.</returns>
 24    public static object SerializeRecord(IRecordable record)
 25    {
 26        return JsonRecordSerializer.Serialize(record, writeIndented: true);
 27    }
 28
 29    /// <summary>
 30    /// Populates a record with data from a serialized payload (JSON string or MemoryPack byte array) based on the speci
 31    /// </summary>
 32    /// <param name="target">The record to populate.</param>
 33    /// <param name="payload">The serialized payload.</param>
 34    public static void PopulateRecord(IRecordable target, object payload)
 35    {
 36        JsonRecordSerializer.Populate(target, (string)payload);
 37    }
 38
 39    /// <summary>
 40    /// Removes an entry from a serialized payload (JSON property or MemoryPack entry) at the specified path based on th
 41    /// </summary>
 42    /// <param name="payload">The serialized payload.</param>
 43    /// <param name="path">The path to the entry to remove.</param>
 44    /// <returns>The modified payload.</returns>
 45    public static object RemovePayloadEntry(object payload, params string[] path)
 46    {
 47        return RemoveJsonProperty((string)payload, path);
 48    }
 49
 50
 51    /// <summary>
 52    /// Sets a value in a serialized payload (JSON property or MemoryPack entry) at the specified path based on the spec
 53    /// </summary>
 54    /// <typeparam name="T">The type of the value to set.</typeparam>
 55    /// <param name="payload">The serialized payload.</param>
 56    /// <param name="value">The value to set.</param>
 57    /// <param name="path">The path to the entry to set.</param>
 58    /// <returns>The modified payload.</returns>
 59    public static object SetPayloadValue<T>(
 60        object payload,
 61        T value,
 62        params string[] path)
 63    {
 64        return SetJsonValue((string)payload, value, path);
 65    }
 66
 67#else
 68
 69    /// <summary>
 70    /// Serializes a record to a payload format (JSON string or MemoryPack byte array) based on the specified options.
 71    /// </summary>
 72    /// <param name="record">The record to serialize.</param>
 73    /// <param name="useMemoryPack">Whether to use MemoryPack for serialization.</param>
 74    /// <returns>The serialized payload.</returns>
 75    public static object SerializeRecord(IRecordable record, bool useMemoryPack = true)
 76    {
 077        return useMemoryPack
 078            ? MemoryPackRecordSerializer.Serialize(record)
 079            : JsonRecordSerializer.Serialize(record, writeIndented: true);
 80    }
 81
 82    /// <summary>
 83    /// Populates a record with data from a serialized payload (JSON string or MemoryPack byte array) based on the speci
 84    /// </summary>
 85    /// <param name="target">The record to populate.</param>
 86    /// <param name="payload">The serialized payload.</param>
 87    ///  <param name="useMemoryPack">Whether to use MemoryPack for deserialization.</param>
 88    public static void PopulateRecord(
 89        IRecordable target,
 90        object payload,
 91        bool useMemoryPack = true)
 92    {
 093        if (useMemoryPack)
 94        {
 095            MemoryPackRecordSerializer.Populate(target, (byte[])payload);
 096            return;
 97        }
 98
 099        JsonRecordSerializer.Populate(target, (string)payload);
 0100    }
 101
 102    /// <summary>
 103    /// Removes an entry from a serialized payload (JSON property or MemoryPack entry) at the specified path based on th
 104    /// </summary>
 105    /// <param name="payload">The serialized payload.</param>
 106    /// <param name="useMemoryPack">Whether to use MemoryPack for deserialization.</param>
 107    /// <param name="path">The path to the entry to remove.</param>
 108    /// <returns>The modified payload.</returns>
 109    public static object RemovePayloadEntry(
 110        object payload,
 111        bool useMemoryPack = true,
 112        params string[] path)
 113    {
 0114        return useMemoryPack
 0115            ? RemoveMemoryPackEntry((byte[])payload, path)
 0116            : RemoveJsonProperty((string)payload, path);
 117    }
 118
 119
 120    /// <summary>
 121    /// Sets a value in a serialized payload (JSON property or MemoryPack entry) at the specified path based on the spec
 122    /// </summary>
 123    /// <typeparam name="T">The type of the value to set.</typeparam>
 124    /// <param name="payload">The serialized payload.</param>
 125    /// <param name="value">The value to set.</param>
 126    /// <param name="useMemoryPack">Whether to use MemoryPack for deserialization.</param>
 127    /// <param name="path">The path to the entry to set.</param>
 128    /// <returns>The modified payload.</returns>
 129    public static object SetPayloadValue<T>(
 130        object payload,
 131        T value,
 132        bool useMemoryPack = true,
 133        params string[] path)
 134    {
 0135        return useMemoryPack
 0136            ? SetMemoryPackValue((byte[])payload, value, path)
 0137            : SetJsonValue((string)payload, value, path);
 138    }
 139
 140#endif
 141
 142    #endregion
 143
 144    #region JSON Editing
 145
 1146    private static readonly JsonSerializerOptions JsonOptions = new()
 1147    {
 1148        IncludeFields = true
 1149    };
 150
 151    /// <summary>
 152    /// Removes a property from a JSON payload at the specified path.
 153    /// </summary>
 154    /// <param name="json">The JSON payload.</param>
 155    /// <param name="path">The path to the property to remove.</param>
 156    /// <returns>The modified JSON payload.</returns>
 157    /// <exception cref="ArgumentException"></exception>
 158    public static string RemoveJsonProperty(string json, params string[] path)
 159    {
 12160        if (path == null || path.Length == 0)
 2161            throw new ArgumentException("A JSON property path is required.", nameof(path));
 162
 10163        JsonObject root = ParseJsonRoot(json);
 9164        JsonObject parent = GetJsonParent(root, path);
 9165        parent.Remove(path[^1]);
 9166        return root.ToJsonString(JsonOptions);
 167    }
 168
 169    /// <summary>
 170    /// Sets a property value in a JSON payload at the specified path.
 171    /// </summary>
 172    /// <typeparam name="T">The type of the value to set.</typeparam>
 173    /// <param name="json">The JSON payload.</param>
 174    /// <param name="value">The value to set.</param>
 175    /// <param name="path">The path to the property to set.</param>
 176    /// <returns>The modified JSON payload.</returns>
 177    /// <exception cref="ArgumentException"></exception>
 178    public static string SetJsonValue<T>(string json, T value, params string[] path)
 179    {
 7180        if (path == null || path.Length == 0)
 2181            throw new ArgumentException("A JSON property path is required.", nameof(path));
 182
 5183        JsonObject root = ParseJsonRoot(json);
 4184        JsonObject parent = GetJsonParent(root, path);
 3185        parent[path[^1]] = JsonSerializer.SerializeToNode(value, JsonOptions);
 3186        return root.ToJsonString(JsonOptions);
 187    }
 188
 189    private static JsonObject ParseJsonRoot(string json)
 190    {
 15191        JsonNode rootNode = JsonNode.Parse(json)
 15192            ?? throw new InvalidOperationException("Unable to parse JSON payload.");
 14193        return rootNode as JsonObject
 14194            ?? throw new InvalidOperationException("Expected JSON root object.");
 195    }
 196
 197    private static JsonObject GetJsonParent(JsonObject root, string[] path)
 198    {
 13199        JsonObject current = root;
 30200        for (int i = 0; i < path.Length - 1; i++)
 201        {
 3202            current = current[path[i]] as JsonObject
 3203                ?? throw new InvalidOperationException(
 3204                    $"Expected JSON object at path segment '{path[i]}'.");
 205        }
 206
 12207        return current;
 208    }
 209
 210    #endregion
 211
 212#if !CHRONICLER_DISABLE_MEMORYPACK
 213
 214    #region MemoryPack Editing
 215
 216    /// <summary>
 217    /// Removes a MemoryPack entry from a serialized payload at the specified path.
 218    /// </summary>
 219    /// <param name="data">The serialized MemoryPack payload.</param>
 220    /// <param name="path">The path to the entry to remove.</param>
 221    /// <returns>The modified serialized MemoryPack payload.</returns>
 222    /// <exception cref="ArgumentException"></exception>
 223    public static byte[] RemoveMemoryPackEntry(byte[] data, params string[] path)
 224    {
 12225        if (path == null || path.Length == 0)
 2226            throw new ArgumentException("A MemoryPack entry path is required.", nameof(path));
 227
 10228        MemoryPackRecordEnvelope envelope = ReadEnvelope(data);
 10229        RemoveMemoryPackEntry(envelope, path, 0);
 9230        return MemoryPackSerializer.Serialize(envelope);
 231    }
 232
 233    /// <summary>
 234    /// Sets a MemoryPack entry value in a serialized payload at the specified path.
 235    /// </summary>
 236    /// <typeparam name="T">The type of the value to set.</typeparam>
 237    /// <param name="data">The serialized MemoryPack payload.</param>
 238    /// <param name="value">The value to set.</param>
 239    /// <param name="path">The path to the entry to set.</param>
 240    /// <returns>The modified serialized MemoryPack payload.</returns>
 241    /// <exception cref="ArgumentException"></exception>
 242    public static byte[] SetMemoryPackValue<T>(byte[] data, T value, params string[] path)
 243    {
 6244        if (path == null || path.Length == 0)
 2245            throw new ArgumentException("A MemoryPack entry path is required.", nameof(path));
 246
 4247        MemoryPackRecordEnvelope envelope = ReadEnvelope(data);
 4248        SetMemoryPackValue(envelope, path, 0, MemoryPackSerializer.Serialize(value));
 3249        return MemoryPackSerializer.Serialize(envelope);
 250    }
 251
 252    private static MemoryPackRecordEnvelope ReadEnvelope(byte[] data)
 253    {
 16254        return MemoryPackSerializer.Deserialize<MemoryPackRecordEnvelope>(data)
 16255            ?? new MemoryPackRecordEnvelope();
 256    }
 257
 258    private static void RemoveMemoryPackEntry(MemoryPackRecordEnvelope envelope, string[] path, int depth)
 259    {
 11260        if (depth == path.Length - 1)
 261        {
 9262            envelope.RemoveEntry(path[depth]);
 9263            return;
 264        }
 265
 2266        if (!envelope.TryGetEntry(path[depth], out byte[]? nestedData)
 2267            || nestedData == null)
 268        {
 1269            throw new InvalidOperationException(
 1270                $"Unable to locate MemoryPack entry '{path[depth]}' at depth {depth}.");
 271        }
 272
 1273        MemoryPackRecordEnvelope nestedEnvelope = ReadEnvelope(nestedData);
 1274        RemoveMemoryPackEntry(nestedEnvelope, path, depth + 1);
 1275        envelope.SetEntry(path[depth], MemoryPackSerializer.Serialize(nestedEnvelope));
 1276    }
 277
 278    private static void SetMemoryPackValue(
 279        MemoryPackRecordEnvelope envelope,
 280        string[] path,
 281        int depth,
 282        byte[] serializedValue)
 283    {
 5284        if (depth == path.Length - 1)
 285        {
 3286            envelope.SetEntry(path[depth], serializedValue);
 3287            return;
 288        }
 289
 2290        if (!envelope.TryGetEntry(path[depth], out byte[]? nestedData)
 2291            || nestedData == null)
 292        {
 1293            throw new InvalidOperationException(
 1294                $"Unable to locate MemoryPack entry '{path[depth]}' at depth {depth}.");
 295        }
 296
 1297        MemoryPackRecordEnvelope nestedEnvelope = ReadEnvelope(nestedData);
 1298        SetMemoryPackValue(nestedEnvelope, path, depth + 1, serializedValue);
 1299        envelope.SetEntry(path[depth], MemoryPackSerializer.Serialize(nestedEnvelope));
 1300    }
 301
 302    #endregion
 303
 304#endif
 305}