using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace Uigenx.Protocol { /// Shared JSON settings for every bridge message. The wire form is camelCase. public static class UgxJson { /// Bumped only for incompatible changes. Unknown message types are ignored, so minors are additive. public const int ProtocolVersion = 1; static readonly JsonSerializerSettings SettingsInstance = Build(); public static JsonSerializerSettings Settings => SettingsInstance; static readonly JsonSerializer SerializerInstance = JsonSerializer.CreateDefault(SettingsInstance); /// Reuse this rather than creating a serializer per message; construction is not cheap. public static JsonSerializer Serializer => SerializerInstance; static JsonSerializerSettings Build() { // ProcessDictionaryKeys must stay false: dictionary keys carry data paths (`player.hp`) // and payload keys that the daemon matches verbatim. var resolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = false, OverrideSpecifiedNames = false, }, }; var settings = new JsonSerializerSettings { ContractResolver = resolver, NullValueHandling = NullValueHandling.Ignore, DateParseHandling = DateParseHandling.None, Formatting = Formatting.None, MissingMemberHandling = MissingMemberHandling.Ignore, }; // No naming strategy on the enum converter: every wire name is spelled out with // [EnumMember] so the JSON text never depends on the C# member spelling. settings.Converters.Add(new StringEnumConverter()); return settings; } public static string Serialize(object value) => JsonConvert.SerializeObject(value, SettingsInstance); public static T Deserialize(string json) => JsonConvert.DeserializeObject(json, SettingsInstance); /// /// Unwraps the JObject and JArray values Newtonsoft leaves inside an /// object valued dictionary into plain dictionaries, lists and primitives, so code /// outside Uigenx.Protocol never has to know about Newtonsoft's tree. /// public static object ToPlain(object value) { switch (value) { case Newtonsoft.Json.Linq.JObject map: { var result = new System.Collections.Generic.Dictionary(map.Count); foreach (var property in map) result[property.Key] = ToPlain(property.Value); return result; } case Newtonsoft.Json.Linq.JArray array: { var result = new System.Collections.Generic.List(array.Count); foreach (var item in array) result.Add(ToPlain(item)); return result; } case Newtonsoft.Json.Linq.JValue primitive: return primitive.Value; default: return value; } } public static bool TryDeserialize(string json, out T value, out string error) { try { value = JsonConvert.DeserializeObject(json, SettingsInstance); error = null; return true; } catch (Exception e) { value = default; error = e.Message; return false; } } } }