From 3a8bfc1b6ab7046c9041be6033db5fc827c4b914 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Fri, 24 Apr 2026 11:50:42 +1000 Subject: [PATCH 1/2] feat(audience): add Json.Serialize(dict, indent) pretty-print overload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second overload indenting nested containers by `indent` spaces per level. Round-trips through Deserialize identically; indent ≤ 0 routes to the existing compact Serialize, so every wire-payload caller is unaffected. Internal scaffolding: - WriteObject / WriteArray / WriteValue thread indent + depth so the overload can emit newlines and leading spaces at the correct nesting level. - AppendNewline helper centralises the "\n + (indent * depth) spaces" pattern. - pretty-path writes ":" with a trailing space; empty containers stay on a single line ({} / []), matching the compact form for collapsed nodes. No behaviour change on the compact overload for any existing caller; the indent threading is inert when indent == 0 (pretty=false). Motivated by the sample-app landing next — its Init tab renders the effective AudienceConfig as an echo view, and the Events/Identity tabs show constructed property dicts, all via Json.Serialize(…, 2). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Packages/Audience/Runtime/Utility/Json.cs | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/Packages/Audience/Runtime/Utility/Json.cs b/src/Packages/Audience/Runtime/Utility/Json.cs index b14e1c0d..7868a027 100644 --- a/src/Packages/Audience/Runtime/Utility/Json.cs +++ b/src/Packages/Audience/Runtime/Utility/Json.cs @@ -10,11 +10,23 @@ internal static class Json internal static string Serialize(Dictionary data) { var sb = new StringBuilder(); - WriteObject(sb, data); + WriteObject(sb, data, indent: 0, depth: 0); return sb.ToString(); } - private static void WriteValue(StringBuilder sb, object value) + // Pretty-prints `data` with `indent` spaces per nesting level. + // Round-trips through Deserialize identically. Indent ≤ 0 returns + // the compact form. Use for human-readable output; wire payloads + // use the compact overload. + internal static string Serialize(Dictionary data, int indent) + { + if (indent <= 0) return Serialize(data); + var sb = new StringBuilder(); + WriteObject(sb, data, indent, depth: 0); + return sb.ToString(); + } + + private static void WriteValue(StringBuilder sb, object value, int indent, int depth) { if (value == null) { @@ -56,11 +68,11 @@ private static void WriteValue(StringBuilder sb, object value) } else if (value is Dictionary dict) { - WriteObject(sb, dict); + WriteObject(sb, dict, indent, depth); } else if (value is IList list) { - WriteArray(sb, list); + WriteArray(sb, list, indent, depth); } else { @@ -68,34 +80,48 @@ private static void WriteValue(StringBuilder sb, object value) } } - private static void WriteObject(StringBuilder sb, Dictionary dict) + private static void WriteObject(StringBuilder sb, Dictionary dict, int indent, int depth) { sb.Append('{'); + if (dict.Count == 0) { sb.Append('}'); return; } + + var pretty = indent > 0; var first = true; foreach (var kvp in dict) { - if (!first) - sb.Append(','); + if (!first) sb.Append(','); first = false; + if (pretty) AppendNewline(sb, indent, depth + 1); WriteString(sb, kvp.Key); - sb.Append(':'); - WriteValue(sb, kvp.Value); + sb.Append(pretty ? ": " : ":"); + WriteValue(sb, kvp.Value, indent, depth + 1); } + if (pretty) AppendNewline(sb, indent, depth); sb.Append('}'); } - private static void WriteArray(StringBuilder sb, IList list) + private static void WriteArray(StringBuilder sb, IList list, int indent, int depth) { sb.Append('['); + if (list.Count == 0) { sb.Append(']'); return; } + + var pretty = indent > 0; for (var i = 0; i < list.Count; i++) { - if (i > 0) - sb.Append(','); - WriteValue(sb, list[i]); + if (i > 0) sb.Append(','); + if (pretty) AppendNewline(sb, indent, depth + 1); + WriteValue(sb, list[i], indent, depth + 1); } + if (pretty) AppendNewline(sb, indent, depth); sb.Append(']'); } + private static void AppendNewline(StringBuilder sb, int indent, int depth) + { + sb.Append('\n'); + sb.Append(' ', indent * depth); + } + private static void WriteString(StringBuilder sb, string s) { sb.Append('"'); From 73d3e467ea7d3ac1cb787bc69e94afefd94a7957 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Fri, 24 Apr 2026 11:50:42 +1000 Subject: [PATCH 2/2] chore(audience): grant IVT to Samples.SampleApp and exclude Tests/Editor from headless build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IVT lets the in-repo SampleApp reach Json.Serialize and JsonReader.DeserializeObject — helpers the public API does not expose. csproj Remove="Editor/**/*.cs" is a no-op today; prereq for the sample-app PR, whose editor tests reference Unity-only types that fail the headless dotnet build. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Packages/Audience/Runtime/AssemblyInfo.cs | 5 +++++ src/Packages/Audience/Tests/Audience.Tests.csproj | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Packages/Audience/Runtime/AssemblyInfo.cs b/src/Packages/Audience/Runtime/AssemblyInfo.cs index 5f1a3ea0..d390acb9 100644 --- a/src/Packages/Audience/Runtime/AssemblyInfo.cs +++ b/src/Packages/Audience/Runtime/AssemblyInfo.cs @@ -2,3 +2,8 @@ [assembly: InternalsVisibleTo("Immutable.Audience.Runtime.Tests")] [assembly: InternalsVisibleTo("Immutable.Audience.Unity")] + +// First-party SampleApp reaches Json.Serialize and +// JsonReader.DeserializeObject; both stay internal so their +// signatures aren't frozen into the public API. +[assembly: InternalsVisibleTo("Immutable.Audience.Samples.SampleApp")] diff --git a/src/Packages/Audience/Tests/Audience.Tests.csproj b/src/Packages/Audience/Tests/Audience.Tests.csproj index a4bc2e18..0194901a 100644 --- a/src/Packages/Audience/Tests/Audience.Tests.csproj +++ b/src/Packages/Audience/Tests/Audience.Tests.csproj @@ -15,4 +15,12 @@ + + + +