diff --git a/ProtectionScan/Features/MainFeature.cs b/ProtectionScan/Features/MainFeature.cs
index c4c206f4..7cee73d1 100644
--- a/ProtectionScan/Features/MainFeature.cs
+++ b/ProtectionScan/Features/MainFeature.cs
@@ -32,6 +32,9 @@ namespace ProtectionScan.Features
#if NETCOREAPP
private const string _jsonName = "json";
internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file");
+
+ private const string _nestedName = "nested";
+ internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file");
#endif
private const string _noArchivesName = "no-archives";
@@ -63,6 +66,11 @@ namespace ProtectionScan.Features
/// Enable JSON output
///
public bool Json { get; private set; }
+
+ ///
+ /// Enable nested JSON output
+ ///
+ public bool Nested { get; private set; }
#endif
public MainFeature()
@@ -73,6 +81,7 @@ namespace ProtectionScan.Features
Add(DebugInput);
Add(FileOnlyInput);
#if NETCOREAPP
+ JsonInput.Add(NestedInput);
Add(JsonInput);
#endif
Add(NoContentsInput);
@@ -93,6 +102,7 @@ namespace ProtectionScan.Features
FileOnly = GetBoolean(_fileOnlyName);
#if NETCOREAPP
Json = GetBoolean(_jsonName);
+ Nested = GetBoolean(_nestedName);
#endif
// Create scanner for all paths
@@ -248,9 +258,62 @@ namespace ProtectionScan.Features
// Attempt to open a protection file for writing
using var jsw = new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.json"));
- // Create the output data
var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true };
- string serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions);
+ string serializedData;
+ if (Nested)
+ {
+ // A nested dictionary is used to achieve proper serialization.
+ var nestedDictionary = new Dictionary();
+ var trimmedPath = path.TrimEnd(['\\', '/']);
+
+ // Sort the keys for consistent output
+ string[] keys = [.. protections.Keys];
+ Array.Sort(keys);
+
+ var modifyNodeList = new List<(Dictionary, string, string[])>();
+
+ // Loop over all keys
+ foreach (string key in keys)
+ {
+ // Skip over files with no protection
+ var value = protections[key];
+ if (value.Count == 0)
+ continue;
+
+ // Sort the detected protections for consistent output
+ string[] fileProtections = [.. value];
+ Array.Sort(fileProtections);
+
+ // Inserts key and protections into nested dictionary, with the key trimmed of the base path.
+ InsertNode(nestedDictionary, key.Substring(trimmedPath.Length), fileProtections, modifyNodeList);
+ }
+
+ // Adds the non-leaf-node protections back in
+ for (int i = 0; i < modifyNodeList.Count; i++)
+ {
+ var copyDictionary = modifyNodeList[i].Item1[modifyNodeList[i].Item2];
+
+ var modifyNode = new List