diff --git a/MinecraftClient/Json.cs b/MinecraftClient/Json.cs
index 494e1142..c16ca4ea 100644
--- a/MinecraftClient/Json.cs
+++ b/MinecraftClient/Json.cs
@@ -1,377 +1,47 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Nodes;
-namespace MinecraftClient
+namespace MinecraftClient;
+
+///
+/// JSON utilities backed by System.Text.Json.
+///
+public static class Json
+{
+ private static readonly JsonSerializerOptions s_escapeOptions = new()
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+
+ ///
+ /// Parse a JSON string into a mutable DOM.
+ ///
+ public static JsonNode? ParseJson(string json) => JsonNode.Parse(json);
+
+ ///
+ /// Escape a string for embedding inside a JSON string literal.
+ /// Uses System.Text.Json serialization and strips the surrounding quotes.
+ ///
+ public static string EscapeString(string src) =>
+ JsonSerializer.Serialize(src, s_escapeOptions)[1..^1];
+}
+
+///
+/// Extension helpers for that replicate the access patterns
+/// of the former JSONData.StringValue property.
+///
+public static class JsonNodeExtensions
{
///
- /// This class parses JSON data and returns an object describing that data.
- /// Really lightweight JSON handling by ORelio - (c) 2013 - 2020
+ /// Return the string representation of any JSON value.
+ /// Strings are returned without quotes; numbers, booleans, and null
+ /// are returned as their text representation.
///
- public static class Json
+ public static string GetStringValue(this JsonNode? node) => node switch
{
- ///
- /// Parse some JSON and return the corresponding JSON object
- ///
- public static JSONData ParseJson(string json)
- {
- int cursorpos = 0;
- return String2Data(json, ref cursorpos);
- }
-
- ///
- /// The class storing unserialized JSON data
- /// The data can be an object, an array or a string
- ///
- public class JSONData
- {
- public enum DataType
- {
- Object,
- Array,
- String
- };
-
- private readonly DataType type;
-
- public DataType Type
- {
- get { return type; }
- }
-
- public Dictionary Properties;
- public List DataArray;
- public string StringValue;
-
- public JSONData(DataType datatype)
- {
- type = datatype;
- Properties = new Dictionary();
- DataArray = new List();
- StringValue = String.Empty;
- }
- }
-
- ///
- /// Parse a JSON string to build a JSON object
- ///
- /// String to parse
- /// Cursor start (set to 0 for function init)
- private static JSONData String2Data(string toparse, ref int cursorpos)
- {
- try
- {
- JSONData data;
- SkipSpaces(toparse, ref cursorpos);
- switch (toparse[cursorpos])
- {
- //Object
- case '{':
- data = new JSONData(JSONData.DataType.Object);
- cursorpos++;
- SkipSpaces(toparse, ref cursorpos);
- while (toparse[cursorpos] != '}')
- {
- if (toparse[cursorpos] == '"')
- {
- JSONData propertyname = String2Data(toparse, ref cursorpos);
- if (toparse[cursorpos] == ':')
- {
- cursorpos++;
- }
- else
- {
- /* parse error ? */
- }
-
- JSONData propertyData = String2Data(toparse, ref cursorpos);
- data.Properties[propertyname.StringValue] = propertyData;
- }
- else cursorpos++;
- }
-
- cursorpos++;
- break;
-
- //Array
- case '[':
- data = new JSONData(JSONData.DataType.Array);
- cursorpos++;
- SkipSpaces(toparse, ref cursorpos);
- while (toparse[cursorpos] != ']')
- {
- if (toparse[cursorpos] == ',')
- {
- cursorpos++;
- }
-
- JSONData arrayItem = String2Data(toparse, ref cursorpos);
- data.DataArray.Add(arrayItem);
- }
-
- cursorpos++;
- break;
-
- //String
- case '"':
- data = new JSONData(JSONData.DataType.String);
- cursorpos++;
- while (toparse[cursorpos] != '"')
- {
- if (toparse[cursorpos] == '\\')
- {
- try //Unicode character \u0123
- {
- if (toparse[cursorpos + 1] == 'u'
- && IsHex(toparse[cursorpos + 2])
- && IsHex(toparse[cursorpos + 3])
- && IsHex(toparse[cursorpos + 4])
- && IsHex(toparse[cursorpos + 5]))
- {
- //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string
- data.StringValue += char.ConvertFromUtf32(int.Parse(
- toparse.Substring(cursorpos + 2, 4),
- System.Globalization.NumberStyles.HexNumber));
- cursorpos += 6;
- continue;
- }
- else if (toparse[cursorpos + 1] == 'n')
- {
- data.StringValue += '\n';
- cursorpos += 2;
- continue;
- }
- else if (toparse[cursorpos + 1] == 'r')
- {
- data.StringValue += '\r';
- cursorpos += 2;
- continue;
- }
- else if (toparse[cursorpos + 1] == 't')
- {
- data.StringValue += '\t';
- cursorpos += 2;
- continue;
- }
- else cursorpos++; //Normal character escapement \"
- }
- catch (IndexOutOfRangeException)
- {
- cursorpos++;
- } // \u01
- catch (ArgumentOutOfRangeException)
- {
- cursorpos++;
- } // Unicode index 0123 was invalid
- }
-
- data.StringValue += toparse[cursorpos];
- cursorpos++;
- }
-
- cursorpos++;
- break;
-
- //Number
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- case '.':
- case '-':
- data = new JSONData(JSONData.DataType.String);
- StringBuilder sb = new();
- while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.' ||
- toparse[cursorpos] == '-')
- {
- sb.Append(toparse[cursorpos]);
- cursorpos++;
- }
-
- data.StringValue = sb.ToString();
- break;
-
- //Boolean : true
- case 't':
- data = new JSONData(JSONData.DataType.String);
- cursorpos++;
- if (toparse[cursorpos] == 'r')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'u')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'e')
- {
- cursorpos++;
- data.StringValue = "true";
- }
-
- break;
-
- //Boolean : false
- case 'f':
- data = new JSONData(JSONData.DataType.String);
- cursorpos++;
- if (toparse[cursorpos] == 'a')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'l')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 's')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'e')
- {
- cursorpos++;
- data.StringValue = "false";
- }
-
- break;
-
- //Null field
- case 'n':
- data = new JSONData(JSONData.DataType.String);
- cursorpos++;
- if (toparse[cursorpos] == 'u')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'l')
- {
- cursorpos++;
- }
-
- if (toparse[cursorpos] == 'l')
- {
- cursorpos++;
- data.StringValue = "null";
- }
-
- break;
-
- //Unknown data
- default:
- cursorpos++;
- return String2Data(toparse, ref cursorpos);
- }
-
- SkipSpaces(toparse, ref cursorpos);
- return data;
- }
- catch (IndexOutOfRangeException)
- {
- return new JSONData(JSONData.DataType.String);
- }
- }
-
- ///
- /// Check if a char is an hexadecimal char (0-9 A-F a-f)
- ///
- /// Char to test
- /// True if hexadecimal
- private static bool IsHex(char c)
- {
- return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'));
- }
-
- ///
- /// Advance the cursor to skip white spaces and line breaks
- ///
- /// String to parse
- /// Cursor position to update
- private static void SkipSpaces(string toparse, ref int cursorpos)
- {
- while (cursorpos < toparse.Length
- && (char.IsWhiteSpace(toparse[cursorpos])
- || toparse[cursorpos] == '\r'
- || toparse[cursorpos] == '\n'))
- cursorpos++;
- }
-
- // Original: https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs
- private static bool NeedEscape(string src, int i)
- {
- var c = src[i];
- return c < 32 || c == '"' || c == '\\'
- // Broken lead surrogate
- || (c is >= '\uD800' and <= '\uDBFF' &&
- (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF'))
- // Broken tail surrogate
- || (c is >= '\uDC00' and <= '\uDFFF' &&
- (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF'))
- // To produce valid JavaScript
- || c == '\u2028' || c == '\u2029'
- // Escape "" for