#nullable enable using IPA.Config.Data; using IPA.Logging; using System; using System.Linq; using System.IO; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; using Boolean = IPA.Config.Data.Boolean; namespace IPA.Config.Providers { internal class JsonConfigProvider : IConfigProvider { public static void RegisterConfig() { Config.Register(); } public string Extension => "json"; public Value? Load(FileInfo file) { if (!file.Exists) return Value.Null(); try { using var fileStream = file.OpenRead(); return VisitToValue(JsonNode.Parse(fileStream)); } catch (Exception e) { Logger.Config.Error($"Error reading JSON file {file.FullName}; ignoring"); Logger.Config.Error(e); return Value.Null(); } } private Value? VisitToValue(JsonNode? node) { if (node == null) return Value.Null(); switch (node.GetValueKind()) { case JsonValueKind.Undefined: Logger.Config.Warn($"Found {nameof(JsonValueKind)}.{nameof(JsonValueKind.Undefined)}"); goto case JsonValueKind.Null; case JsonValueKind.Null: return Value.Null(); case JsonValueKind.True: case JsonValueKind.False: if (node.AsValue().TryGetValue(out var b)) return Value.Bool(b); return Value.Bool(false); case JsonValueKind.String: if (node.AsValue().TryGetValue(out var s)) return Value.Text(s); return Value.Text(string.Empty); case JsonValueKind.Number: var value = node.AsValue(); if (value.TryGetValue(out var l)) return Value.Integer(l); if (value.TryGetValue(out var u)) return Value.Integer((long)u); if (value.TryGetValue(out var dec)) return Value.Float(dec); if (value.TryGetValue(out var dou)) return Value.Float((decimal)dou); if (value.TryGetValue(out var flo)) return Value.Float((decimal)flo); return Value.Float(0); // default to 0 if something breaks case JsonValueKind.Array: return Value.From(node.AsArray().Select(VisitToValue)); case JsonValueKind.Object: return Value.From(node.AsObject() .Select(kvp => new KeyValuePair(kvp.Key, VisitToValue(kvp.Value)))); default: throw new ArgumentException($"Unknown {nameof(JsonValueKind)} in parameter"); } } public void Store(Value value, FileInfo file) { if (!file.Directory.Exists) file.Directory.Create(); try { var jsonNode = VisitToNode(value); using var fileStream = file.Open(FileMode.Create, FileAccess.Write); using var jsonWriter = new Utf8JsonWriter(fileStream, new JsonWriterOptions { Indented = true }); if (jsonNode == null) { jsonWriter.WriteNullValue(); } else { jsonNode.WriteTo(jsonWriter); } } catch (Exception e) { Logger.Config.Error($"Error serializing value for {file.FullName}"); Logger.Config.Error(e); } } private JsonNode? VisitToNode(Value? val) { switch (val) { case Text t: return JsonValue.Create(t.Value); case Boolean b: return JsonValue.Create(b.Value); case Integer i: return JsonValue.Create(i.Value); case FloatingPoint f: return JsonValue.Create(f.Value); case List l: var jarr = new JsonArray(); foreach (var tok in l.Select(VisitToNode)) jarr.Add(tok); return jarr; case Map m: var jobj = new JsonObject(); foreach (var kvp in m) jobj.Add(kvp.Key, VisitToNode(kvp.Value)); return jobj; case null: return null; default: throw new ArgumentException($"Unsupported subtype of {nameof(Value)}"); } } } }