#nullable enable using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using T3.Core.Logging; namespace T3.Serialization; public static class JsonUtils { public static T ReadEnum(JToken? o, string name) where T : struct, Enum { if (o == null) return default; var dirtyFlagJson = o[name]; return dirtyFlagJson != null ? Enum.Parse(dirtyFlagJson.Value() ?? string.Empty) : default; } /// /// A simple wrapper that prevents most exceptions on nulls on malformed definitions and instead returns a default /// public static T? ReadValueSafe(this JToken? token, string name, T? defaultValue = default) { try { var t = token?[name]; return t != null ? t.Value() : defaultValue; } catch { return defaultValue; } } public static List ReadListSafe( this JToken? token, string name, Func? elementConverter = null) { if (token is not JObject obj || obj[name] is not JArray arr) return []; var list = new List(arr.Count); foreach (var e in arr) { if (e == null!) continue; try { var converter = elementConverter != null ? elementConverter(e) : e.Value(); if(converter != null) list.Add( converter ); } catch { // skip malformed element } } return list; } public static T? TryLoadingJson(string filepath) where T : class { if(!TryLoadingJson(filepath, out T? result)) { return default; } return result; } public static bool TryLoadingJson(string filepath, [NotNullWhen(true)] out T? result) { if (!File.Exists(filepath)) { result = default; return false; } try { // File.ReadAllText must stay inside the try: a concurrent writer (e.g. another instance // saving settings on exit) can briefly lock the file. A lock/IO failure here has to fall // back to defaults, not throw out of this "Try" method and crash the caller at startup. var jsonBlob = File.ReadAllText(filepath); var serializer = JsonSerializer.Create(); var fileTextReader = new StringReader(jsonBlob); if (serializer.Deserialize(fileTextReader, typeof(T)) is T configurations) { result = configurations; return true; } Log.Error($"Can't load {filepath}"); result = default; return false; } catch (Exception e) { Log.Error($"Can't load {filepath}:" + e.Message); result = default; return false; } } public static bool TrySaveJson(T dataObject, string filepath) { if (string.IsNullOrEmpty(filepath)) { Log.Warning($"Can't save {typeof(T)} to empty filename..."); return false; } var serializer = JsonSerializer.Create(); serializer.Formatting = Formatting.Indented; try { using var streamWriter = File.CreateText(filepath); serializer.Serialize(streamWriter, dataObject); return true; } catch (Exception e) { Log.Warning($"Can't create file {filepath} to save {typeof(T)} " + e.Message); return false; } } public static void WriteValue(this JsonTextWriter writer, string name, T value) where T : struct { writer.WritePropertyName(name); writer.WriteValue(value); } public static void WriteObject(this JsonTextWriter writer, string name, object value) { writer.WritePropertyName(name); writer.WriteValue(value.ToString()); } public static bool TryGetGuid(JToken? token, out Guid guid) { if (token == null) { guid = Guid.Empty; return false; } var guidString = token.Value(); return Guid.TryParse(guidString, out guid); } public static bool TryReadToken(this JToken? token, [NotNullWhen(true)] out T? result) { result = default; if(token == null ) return false; result = token.Value() ?? default; return result != null; } public static bool TryGetEnumValue(JToken? token, out T enumValue) where T : struct, Enum { enumValue = default; var stringValue = token?.Value(); if (string.IsNullOrEmpty(stringValue)) return false; if (!Enum.TryParse(stringValue, out var result)) return false; enumValue = result; return true; } public static T GetEnumValue(this JToken? token, T fallback = default) where T : struct, Enum { var s = token?.Value(); if (string.IsNullOrEmpty(s)) return default; return Enum.TryParse(s, out var result) ? result : fallback; } public static float SafeFloatFromArray(JArray arr, int i) { if ( arr.Count <= i) return 0f; var t = arr[i]; return t.Type is JTokenType.Float or JTokenType.Integer ? t.Value() : 0f; } } public sealed class SafeEnumConverter : JsonConverter where T : struct, Enum { public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) { try { switch (reader.TokenType) { case JsonToken.String: { var str = reader.Value?.ToString(); if (Enum.TryParse(str, ignoreCase: true, out T result) && Enum.IsDefined(typeof(T), result)) return result; break; } case JsonToken.Integer: { int intVal = Convert.ToInt32(reader.Value); if (Enum.IsDefined(typeof(T), intVal)) return (T)Enum.ToObject(typeof(T), intVal); break; } } } catch { Log.Warning($"Failed to read enum {reader.Value} as {typeof(T)}"); } return default; // fallback value } public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) { writer.WriteValue(value.ToString()); } }