using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace StardewModdingAPI.Toolkit.Serialization.Converters { /// Handles deserialization of . internal class SemanticVersionConverter : JsonConverter { /********* ** Fields *********/ /// Whether to allow non-standard extensions to semantic versioning. protected bool AllowNonStandard { get; set; } /********* ** Accessors *********/ /// Get whether this converter can read JSON. public override bool CanRead { get; } = true; /// Get whether this converter can write JSON. public override bool CanWrite { get; } = true; /********* ** Public methods *********/ /// Get whether this instance can convert the specified object type. /// The object type. public override bool CanConvert(Type objectType) { return typeof(ISemanticVersion).IsAssignableFrom(objectType); } /// Reads the JSON representation of the object. /// The JSON reader. /// The object type. /// The object being read. /// The calling serializer. public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { string path = reader.Path; switch (reader.TokenType) { case JsonToken.StartObject: return this.ReadObject(JObject.Load(reader)); case JsonToken.String: { string? value = JToken.Load(reader).Value(); return value is not null ? this.ReadString(value, path) : null; } default: throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); } } /// Writes the JSON representation of the object. /// The JSON writer. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { writer.WriteValue(value?.ToString()); } /********* ** Private methods *********/ /// Read a JSON object. /// The JSON object to read. private ISemanticVersion ReadObject(JObject obj) { int major = obj.ValueIgnoreCase(nameof(ISemanticVersion.MajorVersion)); int minor = obj.ValueIgnoreCase(nameof(ISemanticVersion.MinorVersion)); int patch = obj.ValueIgnoreCase(nameof(ISemanticVersion.PatchVersion)); string? prereleaseTag = obj.ValueIgnoreCase(nameof(ISemanticVersion.PrereleaseTag)); return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag); } /// Read a JSON string. /// The JSON string value. /// The path to the current JSON node. private ISemanticVersion? ReadString(string str, string path) { if (string.IsNullOrWhiteSpace(str)) return null; if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion? version)) throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); return version; } } }