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 @@ internal sealed class MainFeature : Feature
#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 @@ internal sealed class MainFeature : Feature
/// 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 @@ public MainFeature()
Add(DebugInput);
Add(FileOnlyInput);
#if NETCOREAPP
+ JsonInput.Add(NestedInput);
Add(JsonInput);
#endif
Add(NoContentsInput);
@@ -93,6 +102,7 @@ public override bool Execute()
FileOnly = GetBoolean(_fileOnlyName);
#if NETCOREAPP
Json = GetBoolean(_jsonName);
+ Nested = GetBoolean(_nestedName);
#endif
// Create scanner for all paths
@@ -248,9 +258,62 @@ private void WriteProtectionResultJson(string path, 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