You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

136 lines
4.9 KiB

  1. #nullable enable
  2. using IPA.Config.Data;
  3. using IPA.Logging;
  4. using System;
  5. using System.Linq;
  6. using System.IO;
  7. using System.Collections.Generic;
  8. using System.Text.Json;
  9. using System.Text.Json.Nodes;
  10. using Boolean = IPA.Config.Data.Boolean;
  11. namespace IPA.Config.Providers
  12. {
  13. internal class JsonConfigProvider : IConfigProvider
  14. {
  15. public static void RegisterConfig()
  16. {
  17. Config.Register<JsonConfigProvider>();
  18. }
  19. public string Extension => "json";
  20. public Value? Load(FileInfo file)
  21. {
  22. if (!file.Exists) return Value.Null();
  23. try
  24. {
  25. using var fileStream = file.OpenRead();
  26. return VisitToValue(JsonNode.Parse(fileStream));
  27. }
  28. catch (Exception e)
  29. {
  30. Logger.Config.Error($"Error reading JSON file {file.FullName}; ignoring");
  31. Logger.Config.Error(e);
  32. return Value.Null();
  33. }
  34. }
  35. private Value? VisitToValue(JsonNode? node)
  36. {
  37. if (node == null) return Value.Null();
  38. switch (node.GetValueKind())
  39. {
  40. case JsonValueKind.Undefined:
  41. Logger.Config.Warn($"Found {nameof(JsonValueKind)}.{nameof(JsonValueKind.Undefined)}");
  42. goto case JsonValueKind.Null;
  43. case JsonValueKind.Null:
  44. return Value.Null();
  45. case JsonValueKind.True:
  46. case JsonValueKind.False:
  47. if (node.AsValue().TryGetValue<bool>(out var b))
  48. return Value.Bool(b);
  49. return Value.Bool(false);
  50. case JsonValueKind.String:
  51. if (node.AsValue().TryGetValue<string>(out var s))
  52. return Value.Text(s);
  53. return Value.Text(string.Empty);
  54. case JsonValueKind.Number:
  55. var value = node.AsValue();
  56. if (value.TryGetValue<long>(out var l))
  57. return Value.Integer(l);
  58. if (value.TryGetValue<ulong>(out var u))
  59. return Value.Integer((long)u);
  60. if (value.TryGetValue<decimal>(out var dec))
  61. return Value.Float(dec);
  62. if (value.TryGetValue<double>(out var dou))
  63. return Value.Float((decimal)dou);
  64. if (value.TryGetValue<float>(out var flo))
  65. return Value.Float((decimal)flo);
  66. return Value.Float(0); // default to 0 if something breaks
  67. case JsonValueKind.Array:
  68. return Value.From(node.AsArray().Select(VisitToValue));
  69. case JsonValueKind.Object:
  70. return Value.From(node.AsObject()
  71. .Select(kvp => new KeyValuePair<string, Value?>(kvp.Key, VisitToValue(kvp.Value))));
  72. default:
  73. throw new ArgumentException($"Unknown {nameof(JsonValueKind)} in parameter");
  74. }
  75. }
  76. public void Store(Value value, FileInfo file)
  77. {
  78. if (!file.Directory.Exists)
  79. file.Directory.Create();
  80. try
  81. {
  82. var jsonNode = VisitToNode(value);
  83. using var fileStream = file.Open(FileMode.Create, FileAccess.Write);
  84. using var jsonWriter = new Utf8JsonWriter(fileStream, new JsonWriterOptions { Indented = true });
  85. if (jsonNode == null)
  86. {
  87. jsonWriter.WriteNullValue();
  88. }
  89. else
  90. {
  91. jsonNode.WriteTo(jsonWriter);
  92. }
  93. }
  94. catch (Exception e)
  95. {
  96. Logger.Config.Error($"Error serializing value for {file.FullName}");
  97. Logger.Config.Error(e);
  98. }
  99. }
  100. private JsonNode? VisitToNode(Value? val)
  101. {
  102. switch (val)
  103. {
  104. case Text t:
  105. return JsonValue.Create(t.Value);
  106. case Boolean b:
  107. return JsonValue.Create(b.Value);
  108. case Integer i:
  109. return JsonValue.Create(i.Value);
  110. case FloatingPoint f:
  111. return JsonValue.Create(f.Value);
  112. case List l:
  113. var jarr = new JsonArray();
  114. foreach (var tok in l.Select(VisitToNode)) jarr.Add(tok);
  115. return jarr;
  116. case Map m:
  117. var jobj = new JsonObject();
  118. foreach (var kvp in m) jobj.Add(kvp.Key, VisitToNode(kvp.Value));
  119. return jobj;
  120. case null:
  121. return null;
  122. default:
  123. throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
  124. }
  125. }
  126. }
  127. }