From 81bbec01e2659b12d73ced92c7dfe8fd0b8375e5 Mon Sep 17 00:00:00 2001 From: Igor Stojkovic Date: Mon, 9 Jun 2025 13:36:21 +0200 Subject: [PATCH 1/2] Fixed JsonUtility implementation for CLI so it can work with private fields --- src/NuGetForUnity.Cli/Fakes/JsonUtility.cs | 98 +++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs b/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs index 881ad8ff..c2876fc5 100644 --- a/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs +++ b/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs @@ -1,14 +1,38 @@ #nullable enable +using System; +using System.Collections.Generic; +using System.Reflection; using System.Text.Json; +using System.Text.Json.Serialization; +using NugetForUnity.Configuration; namespace UnityEngine { internal static class JsonUtility { + private static JsonSerializerOptions? options; + + private static JsonSerializerOptions Options + { + get + { + if (options != null) + { + return options; + } + + options = new JsonSerializerOptions { IncludeFields = true }; + options.Converters.Add(new PrivateFieldConverter()); + options.Converters.Add(new PrivateFieldConverter()); + + return options; + } + } + public static T? FromJson(string output) { - return JsonSerializer.Deserialize(output, new JsonSerializerOptions { IncludeFields = true }); + return JsonSerializer.Deserialize(output, Options); } public static void FromJsonOverwrite(string jsonString, T target) @@ -18,7 +42,77 @@ public static void FromJsonOverwrite(string jsonString, T target) internal static string ToJson(T value, bool pretty = false) { - return JsonSerializer.Serialize(value, new JsonSerializerOptions { IncludeFields = true, WriteIndented = pretty }); + Options.WriteIndented = pretty; + return JsonSerializer.Serialize(value, Options); + } + + private class PrivateFieldConverter : JsonConverter + where T : new() + { + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var obj = new T(); + + var fieldMap = new Dictionary(); + foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) + { + fieldMap[field.Name] = field; + } + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return obj; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + var propName = reader.GetString(); + + if (propName == null || !reader.Read()) + { + throw new JsonException(); + } + + if (fieldMap.TryGetValue(propName, out var field)) + { + var value = JsonSerializer.Deserialize(ref reader, field.FieldType, options); + field.SetValue(obj, value); + } + else + { + // Skip unknown field + reader.Skip(); + } + } + + throw new JsonException("Incomplete JSON object"); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + var fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + foreach (var field in fields) + { + var fieldValue = field.GetValue(value); + writer.WritePropertyName(field.Name); + JsonSerializer.Serialize(writer, fieldValue, fieldValue?.GetType() ?? typeof(object), options); + } + + writer.WriteEndObject(); + } } } } From d4f46ac48ea725050002ee7c7c7c37074c78ea7a Mon Sep 17 00:00:00 2001 From: JoC0de <53140583+JoC0de@users.noreply.github.com> Date: Sat, 14 Jun 2025 11:24:53 +0200 Subject: [PATCH 2/2] fix usage of static json options as thy get imutable after they are used once + better error messages --- src/NuGetForUnity.Cli/Fakes/JsonUtility.cs | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs b/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs index c2876fc5..dd98959f 100644 --- a/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs +++ b/src/NuGetForUnity.Cli/Fakes/JsonUtility.cs @@ -13,6 +13,8 @@ internal static class JsonUtility { private static JsonSerializerOptions? options; + private static JsonSerializerOptions? prettyOptions; + private static JsonSerializerOptions Options { get @@ -30,6 +32,8 @@ private static JsonSerializerOptions Options } } + private static JsonSerializerOptions PrettyOptions => prettyOptions ??= new JsonSerializerOptions(Options) { WriteIndented = true }; + public static T? FromJson(string output) { return JsonSerializer.Deserialize(output, Options); @@ -42,21 +46,21 @@ public static void FromJsonOverwrite(string jsonString, T target) internal static string ToJson(T value, bool pretty = false) { - Options.WriteIndented = pretty; - return JsonSerializer.Serialize(value, Options); + return JsonSerializer.Serialize(value, pretty ? PrettyOptions : Options); } - private class PrivateFieldConverter : JsonConverter + // special converter for classes with private fields e.g. NativeRuntimeSettings and NativeRuntimeAssetConfiguration + private sealed class PrivateFieldConverter : JsonConverter where T : new() { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { - throw new JsonException(); + throw new JsonException($"Expected first token to be {nameof(JsonTokenType.StartObject)} but got {reader.TokenType}"); } - var obj = new T(); + var result = new T(); var fieldMap = new Dictionary(); foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) @@ -68,25 +72,25 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial { if (reader.TokenType == JsonTokenType.EndObject) { - return obj; + return result; } if (reader.TokenType != JsonTokenType.PropertyName) { - throw new JsonException(); + throw new JsonException($"Expected {nameof(JsonTokenType.PropertyName)} but got {reader.TokenType}"); } var propName = reader.GetString(); if (propName == null || !reader.Read()) { - throw new JsonException(); + throw new JsonException("Expected the name of the property but got null"); } if (fieldMap.TryGetValue(propName, out var field)) { var value = JsonSerializer.Deserialize(ref reader, field.FieldType, options); - field.SetValue(obj, value); + field.SetValue(result, value); } else { @@ -95,7 +99,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial } } - throw new JsonException("Incomplete JSON object"); + throw new JsonException($"Incomplete JSON object. Not reached {nameof(JsonTokenType.EndObject)}"); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)