diff --git a/src/LuYao.Common/Text/Json/JsonReader.cs b/src/LuYao.Common/Text/Json/JsonReader.cs deleted file mode 100644 index 0f4ed31..0000000 --- a/src/LuYao.Common/Text/Json/JsonReader.cs +++ /dev/null @@ -1,442 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -namespace LuYao.Text.Json; - -/// -/// 高性能 JSON 读取器,使用固定大小缓冲区优化性能 -/// -public sealed class JsonReader : IDisposable -{ - private readonly TextReader _reader; - private readonly bool _ownsReader; - private char[] _buffer; - private int _bufferPosition; - private int _bufferLength; - private int _currentIndex; - private int _line = 1; - private int _column = 1; - private bool _disposed; - - private const int DefaultBufferSize = 4096; - - /// - /// 初始化 JsonReader 实例 - /// - /// 要读取的 TextReader - /// 是否拥有并释放 TextReader - /// 缓冲区大小 - public JsonReader(TextReader reader, bool ownsReader = false, int bufferSize = DefaultBufferSize) - { - _reader = reader ?? throw new ArgumentNullException(nameof(reader)); - _ownsReader = ownsReader; - _buffer = new char[Math.Max(bufferSize, 64)]; - } - - /// - /// 使用 JSON 字符串初始化 JsonReader 实例 - /// - /// 要解析的 JSON 字符串 - public JsonReader(string json) : this(new StringReader(json), true) - { - } - - /// - /// 当前标记类型 - /// - public JsonTokenType TokenType { get; private set; } = JsonTokenType.None; - - /// - /// 当前标记值 - /// - public object? Value { get; private set; } - - /// - /// 当前行号 - /// - public int Line => _line; - - /// - /// 当前列号 - /// - public int Column => _column; - - /// - /// 读取下一个标记 - /// - /// 如果成功读取到标记则返回 true,否则返回 false - public bool Read() - { - if (_disposed) throw new ObjectDisposedException(nameof(JsonReader)); - - SkipWhitespace(); - - if (!EnsureBuffer()) - { - TokenType = JsonTokenType.None; - Value = null; - return false; - } - - char c = _buffer[_bufferPosition]; - - switch (c) - { - case '{': - TokenType = JsonTokenType.StartObject; - Value = null; - ConsumeChar(); - return true; - - case '}': - TokenType = JsonTokenType.EndObject; - Value = null; - ConsumeChar(); - return true; - - case '[': - TokenType = JsonTokenType.StartArray; - Value = null; - ConsumeChar(); - return true; - - case ']': - TokenType = JsonTokenType.EndArray; - Value = null; - ConsumeChar(); - return true; - - case '"': - return ReadString(); - - case 't': - case 'f': - return ReadBoolean(); - - case 'n': - return ReadNull(); - - case ',': - case ':': - ConsumeChar(); - return Read(); // 跳过分隔符,读取下一个标记 - - default: - if (IsDigit(c) || c == '-' || c == '+') - return ReadNumber(); - - throw new JsonException($"Unexpected character '{c}' at line {_line}, column {_column}"); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SkipWhitespace() - { - while (EnsureBuffer()) - { - char c = _buffer[_bufferPosition]; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - { - ConsumeChar(); - } - else - { - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConsumeChar() - { - if (_buffer[_bufferPosition] == '\n') - { - _line++; - _column = 1; - } - else - { - _column++; - } - - _bufferPosition++; - _currentIndex++; - } - - private bool EnsureBuffer() - { - if (_bufferPosition >= _bufferLength) - { - _bufferLength = _reader.Read(_buffer, 0, _buffer.Length); - _bufferPosition = 0; - - if (_bufferLength == 0) - return false; - } - return true; - } - - private bool ReadString() - { - ConsumeChar(); // 消费开始的引号 - - var sb = new StringBuilder(); - - while (EnsureBuffer()) - { - char c = _buffer[_bufferPosition]; - - if (c == '"') - { - ConsumeChar(); // 消费结束的引号 - - // 判断是否为属性名(简化判断:查看下一个非空白字符是否为冒号) - int savedPos = _bufferPosition; - int savedLine = _line; - int savedColumn = _column; - - SkipWhitespace(); - bool isPropertyName = EnsureBuffer() && _buffer[_bufferPosition] == ':'; - - // 恢复位置和行列号 - _bufferPosition = savedPos; - _line = savedLine; - _column = savedColumn; - - TokenType = isPropertyName ? JsonTokenType.PropertyName : JsonTokenType.String; - Value = sb.ToString(); - return true; - } - else if (c == '\\') - { - ConsumeChar(); - if (!EnsureBuffer()) - throw new JsonException("Unexpected end of input while reading string escape sequence"); - - char escaped = _buffer[_bufferPosition]; - ConsumeChar(); - - switch (escaped) - { - case '"': sb.Append('"'); break; - case '\\': sb.Append('\\'); break; - case '/': sb.Append('/'); break; - case 'b': sb.Append('\b'); break; - case 'f': sb.Append('\f'); break; - case 'n': sb.Append('\n'); break; - case 'r': sb.Append('\r'); break; - case 't': sb.Append('\t'); break; - case 'u': - // Unicode 转义序列 \uXXXX - string hexDigits = ReadHexDigits(4); - if (int.TryParse(hexDigits, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int unicode)) - { - sb.Append((char)unicode); - } - else - { - throw new JsonException($"Invalid unicode escape sequence \\u{hexDigits}"); - } - break; - default: - throw new JsonException($"Invalid escape sequence \\{escaped}"); - } - } - else if (char.IsControl(c)) - { - throw new JsonException($"Unescaped control character in string at line {_line}, column {_column}"); - } - else - { - sb.Append(c); - ConsumeChar(); - } - } - - throw new JsonException("Unexpected end of input while reading string"); - } - - private string ReadHexDigits(int count) - { - var sb = new StringBuilder(count); - for (int i = 0; i < count; i++) - { - if (!EnsureBuffer()) - throw new JsonException("Unexpected end of input while reading unicode escape sequence"); - - char c = _buffer[_bufferPosition]; - if (!IsHexDigit(c)) - throw new JsonException($"Invalid hex digit '{c}' in unicode escape sequence"); - - sb.Append(c); - ConsumeChar(); - } - return sb.ToString(); - } - - private bool ReadNumber() - { - var sb = new StringBuilder(); - bool hasDecimalPoint = false; - bool hasExponent = false; - - // 处理负号 - if (EnsureBuffer() && (_buffer[_bufferPosition] == '-' || _buffer[_bufferPosition] == '+')) - { - sb.Append(_buffer[_bufferPosition]); - ConsumeChar(); - } - - // 读取数字部分 - bool hasDigits = false; - while (EnsureBuffer()) - { - char c = _buffer[_bufferPosition]; - - if (IsDigit(c)) - { - sb.Append(c); - hasDigits = true; - ConsumeChar(); - } - else if (c == '.' && !hasDecimalPoint && !hasExponent) - { - hasDecimalPoint = true; - sb.Append(c); - ConsumeChar(); - } - else if ((c == 'e' || c == 'E') && !hasExponent && hasDigits) - { - hasExponent = true; - sb.Append(c); - ConsumeChar(); - - // 指数部分可能有符号 - if (EnsureBuffer() && (_buffer[_bufferPosition] == '+' || _buffer[_bufferPosition] == '-')) - { - sb.Append(_buffer[_bufferPosition]); - ConsumeChar(); - } - } - else - { - break; - } - } - - if (!hasDigits) - throw new JsonException($"Invalid number format at line {_line}, column {_column}"); - - string numberStr = sb.ToString(); - TokenType = JsonTokenType.Number; - - // 尝试解析为不同的数字类型 - if (!hasDecimalPoint && !hasExponent) - { - if (long.TryParse(numberStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out long longValue)) - { - Value = longValue; - return true; - } - } - - if (double.TryParse(numberStr, NumberStyles.Float, CultureInfo.InvariantCulture, out double doubleValue)) - { - Value = doubleValue; - return true; - } - - throw new JsonException($"Invalid number format '{numberStr}' at line {_line}, column {_column}"); - } - - private bool ReadBoolean() - { - if (ReadLiteral("true")) - { - TokenType = JsonTokenType.Boolean; - Value = true; - return true; - } - - if (ReadLiteral("false")) - { - TokenType = JsonTokenType.Boolean; - Value = false; - return true; - } - - throw new JsonException($"Invalid boolean value at line {_line}, column {_column}"); - } - - private bool ReadNull() - { - if (ReadLiteral("null")) - { - TokenType = JsonTokenType.Null; - Value = null; - return true; - } - - throw new JsonException($"Invalid null value at line {_line}, column {_column}"); - } - - private bool ReadLiteral(string literal) - { - for (int i = 0; i < literal.Length; i++) - { - if (!EnsureBuffer() || _buffer[_bufferPosition] != literal[i]) - return false; - ConsumeChar(); - } - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsDigit(char c) => c >= '0' && c <= '9'; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsHexDigit(char c) => - (c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f'); - - /// - /// 释放资源 - /// - public void Dispose() - { - if (!_disposed) - { - _buffer = null!; - - if (_ownsReader) - { - _reader?.Dispose(); - } - - _disposed = true; - } - } -} - -/// -/// JSON 解析异常 -/// -public class JsonException : Exception -{ - /// - /// 使用错误消息初始化 JsonException 实例 - /// - /// 错误消息 - public JsonException(string message) : base(message) { } - - /// - /// 使用错误消息和内部异常初始化 JsonException 实例 - /// - /// 错误消息 - /// 内部异常 - public JsonException(string message, Exception innerException) : base(message, innerException) { } -} \ No newline at end of file diff --git a/src/LuYao.Common/Text/Json/JsonWriter.cs b/src/LuYao.Common/Text/Json/JsonWriter.cs deleted file mode 100644 index f86698d..0000000 --- a/src/LuYao.Common/Text/Json/JsonWriter.cs +++ /dev/null @@ -1,431 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -namespace LuYao.Text.Json; - -/// -/// 高性能 JSON 写入器,使用固定大小缓冲区和 Span 优化 -/// -public sealed class JsonWriter : IDisposable -{ - private readonly TextWriter _writer; - private readonly bool _ownsWriter; - private char[]? _buffer; - private int _bufferPosition; - private bool _disposed; - - private const int DefaultBufferSize = 4096; - - // JSON 状态栈,用于管理嵌套结构 - private JsonWriteState[] _stateStack; - private int _stackDepth; - - // 格式化选项 - private readonly bool _indented; - private readonly string _indentString; - private readonly bool _spaceAfterComma; - private int _currentIndentLevel; - - /// - /// 初始化 JsonWriter 实例 - /// - /// 要写入的 TextWriter - /// 是否拥有并释放 TextWriter - /// 是否启用缩进格式化 - /// 缩进字符串 - /// 是否在逗号后添加空格(仅在非缩进模式下) - /// 缓冲区大小 - public JsonWriter(TextWriter writer, bool ownsWriter = false, bool indented = false, - string indentString = " ", bool spaceAfterComma = false, int bufferSize = DefaultBufferSize) - { - _writer = writer ?? throw new ArgumentNullException(nameof(writer)); - _ownsWriter = ownsWriter; - _indented = indented; - _indentString = indentString ?? " "; - _spaceAfterComma = spaceAfterComma && !indented; // 缩进模式下忽略此选项 - _buffer = new char[Math.Max(bufferSize, 64)]; - _stateStack = new JsonWriteState[8]; // 初始栈大小 - } - - /// - /// 使用 StringBuilder 初始化 JsonWriter 实例 - /// - /// 要写入的 StringBuilder - /// 是否启用缩进格式化 - /// 缩进字符串 - /// 是否在逗号后添加空格(仅在非缩进模式下) - public JsonWriter(StringBuilder sb, bool indented = false, string indentString = " ", bool spaceAfterComma = false) - : this(new StringWriter(sb), true, indented, indentString, spaceAfterComma) - { - } - - private enum JsonWriteState - { - Start, - Object, - Array, - Property, - Value - } - - /// - /// 写入对象开始符 { - /// - public void WriteStartObject() - { - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteChar('{'); - PushState(JsonWriteState.Object); - IncreaseIndent(); - if (_indented) WriteNewLine(); // 在增加缩进级别后再换行 - } - - /// - /// 写入对象结束符 } - /// - public void WriteEndObject() - { - PopState(JsonWriteState.Object); - DecreaseIndent(); - if (_indented) WriteNewLine(); // 先换行再写缩进 - WriteIndentIfNeeded(); - WriteChar('}'); - } - - /// - /// 写入数组开始符 [ - /// - public void WriteStartArray() - { - WriteValueSeparator(); - WriteChar('['); - PushState(JsonWriteState.Array); - IncreaseIndent(); - if (_indented) WriteNewLine(); // 在增加缩进级别后再换行 - } - - /// - /// 写入数组结束符 ] - /// - public void WriteEndArray() - { - PopState(JsonWriteState.Array); - DecreaseIndent(); - if (_indented) WriteNewLine(); // 先换行再写缩进 - WriteIndentIfNeeded(); - WriteChar(']'); - } - - /// - /// 写入属性名 - /// - public void WritePropertyName(string? name) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteStringEscaped(name); - WriteChar(':'); - if (_indented) WriteChar(' '); - - SetState(JsonWriteState.Property); - } - - /// - /// 写入字符串值 - /// - public void WriteValue(string? value) - { - WriteValueSeparator(); - - if (value == null) - { - WriteNull(); - } - else - { - WriteStringEscaped(value); - } - - SetState(JsonWriteState.Value); - } - - /// - /// 写入整数值 - /// - public void WriteValue(long value) - { - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteString(value.ToString(CultureInfo.InvariantCulture)); - SetState(JsonWriteState.Value); - } - - /// - /// 写入浮点值 - /// - public void WriteValue(double value) - { - WriteValueSeparator(); - WriteIndentIfNeeded(); - - if (double.IsNaN(value) || double.IsInfinity(value)) - { - WriteNull(); - } - else - { - WriteString(value.ToString("R", CultureInfo.InvariantCulture)); - } - - SetState(JsonWriteState.Value); - } - - /// - /// 写入布尔值 - /// - public void WriteValue(bool value) - { - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteString(value ? "true" : "false"); - SetState(JsonWriteState.Value); - } - - /// - /// 写入 null 值 - /// - public void WriteNull() - { - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteString("null"); - SetState(JsonWriteState.Value); - } - - /// - /// 写入原始 JSON - /// - public void WriteRaw(string? json) - { - if (json == null) - throw new ArgumentNullException(nameof(json)); - - WriteValueSeparator(); - WriteIndentIfNeeded(); - WriteString(json); - SetState(JsonWriteState.Value); - } - - /// - /// 刷新缓冲区 - /// - public void Flush() - { - if (_disposed || _buffer is null) - return; - - if (_bufferPosition > 0) - { - _writer.Write(_buffer, 0, _bufferPosition); - _bufferPosition = 0; - } - _writer.Flush(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteChar(char c) - { - EnsureCapacity(1); - _buffer![_bufferPosition++] = c; - } - - /// - /// 写入原始字符串,不进行转义 - /// - private void WriteString(string? str) - { - if (str == null) return; - - EnsureCapacity(str.Length); - for (int i = 0; i < str.Length; i++) - { - _buffer![_bufferPosition++] = str[i]; - } - } - - /// - /// 写入字符串,进行JSON转义 - /// - private void WriteStringEscaped(string value) - { - if (value == null) return; - - WriteChar('"'); - - foreach (char c in value) - { - switch (c) - { - case '"': - WriteString("\\\""); - break; - case '\\': - WriteString("\\\\"); - break; - case '\b': - WriteString("\\b"); - break; - case '\f': - WriteString("\\f"); - break; - case '\n': - WriteString("\\n"); - break; - case '\r': - WriteString("\\r"); - break; - case '\t': - WriteString("\\t"); - break; - default: - if (char.IsControl(c)) - { - WriteString($"\\u{(int)c:x4}"); - } - else - { - WriteChar(c); - } - break; - } - } - - WriteChar('"'); - } - - private void EnsureCapacity(int count) - { - if (_bufferPosition + count >= _buffer!.Length) - { - Flush(); - } - } - - private void WriteValueSeparator() - { - if (_stackDepth > 0) - { - var currentState = _stateStack[_stackDepth - 1]; - if (currentState == JsonWriteState.Array || currentState == JsonWriteState.Object) - { - // 第一个元素不需要逗号 - _stateStack[_stackDepth - 1] = JsonWriteState.Value; - } - else if (currentState == JsonWriteState.Value) - { - WriteChar(','); - if (_indented) - { - WriteNewLine(); - } - else if (_spaceAfterComma) - { - WriteChar(' '); - } - } - // Property 状态不需要逗号,只需要设置为 Value - else if (currentState == JsonWriteState.Property) - { - // 属性名后面跟值,不需要逗号 - } - } - } - - private void WriteIndentIfNeeded() - { - if (_indented && _currentIndentLevel > 0) - { - for (int i = 0; i < _currentIndentLevel; i++) - { - WriteString(_indentString); - } - } - } - - private void WriteNewLine() - { - WriteChar('\n'); - } - - private void PushState(JsonWriteState state) - { - if (_stackDepth >= _stateStack.Length) - { - Array.Resize(ref _stateStack, _stateStack.Length * 2); - } - _stateStack[_stackDepth++] = state; - } - - private void PopState(JsonWriteState expectedState) - { - if (_stackDepth == 0) - throw new InvalidOperationException("Invalid JSON structure: unexpected end marker"); - - var currentState = _stateStack[--_stackDepth]; - // 允许从 Value 或 Property 状态退出 Object/Array,因为这些都是有效的状态 - if (currentState != expectedState && currentState != JsonWriteState.Value && - !(expectedState == JsonWriteState.Object && currentState == JsonWriteState.Property)) - throw new InvalidOperationException($"Invalid JSON structure: expected {expectedState}, got {currentState}"); - } - - private void SetState(JsonWriteState state) - { - if (_stackDepth > 0) - { - _stateStack[_stackDepth - 1] = state; - } - } - - private void IncreaseIndent() - { - if (_indented) - { - _currentIndentLevel++; - } - } - - private void DecreaseIndent() - { - if (_indented) - { - _currentIndentLevel--; - } - } - - /// - /// 释放资源 - /// - public void Dispose() - { - if (!_disposed) - { - Flush(); - - _buffer = null; - - if (_ownsWriter) - { - _writer?.Dispose(); - } - - _disposed = true; - } - } -} \ No newline at end of file diff --git a/tests/LuYao.Common.UnitTests/Text/Json/JsonReaderTest.cs b/tests/LuYao.Common.UnitTests/Text/Json/JsonReaderTest.cs deleted file mode 100644 index d5b12db..0000000 --- a/tests/LuYao.Common.UnitTests/Text/Json/JsonReaderTest.cs +++ /dev/null @@ -1,567 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace LuYao.Text.Json; - -[TestClass] -public class JsonReaderTest -{ - [TestMethod] - public void Constructor_WithTextReader_ShouldInitializeCorrectly() - { - // Arrange - var textReader = new StringReader("{}"); - - // Act - using var jsonReader = new JsonReader(textReader); - - // Assert - Assert.AreEqual(JsonTokenType.None, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - Assert.AreEqual(1, jsonReader.Line); - Assert.AreEqual(1, jsonReader.Column); - } - - [TestMethod] - public void Constructor_WithString_ShouldInitializeCorrectly() - { - // Arrange - string json = "{}"; - - // Act - using var jsonReader = new JsonReader(json); - - // Assert - Assert.AreEqual(JsonTokenType.None, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - Assert.AreEqual(1, jsonReader.Line); - Assert.AreEqual(1, jsonReader.Column); - } - - [TestMethod] - public void Constructor_WithNullTextReader_ShouldThrowArgumentNullException() - { - // Act & Assert - Assert.Throws(() => new JsonReader((TextReader)null)); - } - - [TestMethod] - public void Read_EmptyObject_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("{}"); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.StartObject, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.EndObject, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - - Assert.IsFalse(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.None, jsonReader.TokenType); - } - - [TestMethod] - public void Read_EmptyArray_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("[]"); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.StartArray, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.EndArray, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - - Assert.IsFalse(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.None, jsonReader.TokenType); - } - - [TestMethod] - public void Read_SimpleString_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("\"hello\""); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("hello", jsonReader.Value); - - Assert.IsFalse(jsonReader.Read()); - } - - [TestMethod] - public void Read_PropertyName_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("{\"name\":\"value\"}"); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); // { - Assert.AreEqual(JsonTokenType.StartObject, jsonReader.TokenType); - - Assert.IsTrue(jsonReader.Read()); // "name" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("name", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // "value" - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("value", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // } - Assert.AreEqual(JsonTokenType.EndObject, jsonReader.TokenType); - } - - [TestMethod] - public void Read_StringWithEscapes_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("\"hello\\nworld\\t\\\"test\\\"\""); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("hello\nworld\t\"test\"", jsonReader.Value); - } - - [TestMethod] - public void Read_StringWithUnicodeEscape_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("\"\\u0048\\u0065\\u006C\\u006C\\u006F\""); // "Hello" - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("Hello", jsonReader.Value); - } - - [TestMethod] - public void Read_StringWithInvalidUnicodeEscape_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("\"\\uGGGG\""); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_StringWithInvalidEscape_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("\"\\x\""); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_UnterminatedString_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("\"unterminated"); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_PositiveInteger_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("123"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(123L, jsonReader.Value); - } - - [TestMethod] - public void Read_NegativeInteger_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("-456"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(-456L, jsonReader.Value); - } - - [TestMethod] - public void Read_PositiveDecimal_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("123.45"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(123.45, jsonReader.Value); - } - - [TestMethod] - public void Read_NegativeDecimal_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("-123.45"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(-123.45, jsonReader.Value); - } - - [TestMethod] - public void Read_NumberWithExponent_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("1.23e4"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(12300.0, jsonReader.Value); - } - - [TestMethod] - public void Read_NumberWithNegativeExponent_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("1.23e-4"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(0.000123, jsonReader.Value); - } - - [TestMethod] - public void Read_InvalidNumber_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("-"); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_BooleanTrue_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("true"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Boolean, jsonReader.TokenType); - Assert.AreEqual(true, jsonReader.Value); - } - - [TestMethod] - public void Read_BooleanFalse_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("false"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Boolean, jsonReader.TokenType); - Assert.AreEqual(false, jsonReader.Value); - } - - [TestMethod] - public void Read_InvalidBoolean_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("tru"); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_Null_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("null"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Null, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - } - - [TestMethod] - public void Read_InvalidNull_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("nul"); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_ComplexObject_ShouldReadCorrectly() - { - // Arrange - string json = "{\"name\":\"John\",\"age\":30,\"active\":true,\"scores\":[85,92,78],\"address\":null}"; - using var jsonReader = new JsonReader(json); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); // { - Assert.AreEqual(JsonTokenType.StartObject, jsonReader.TokenType); - - Assert.IsTrue(jsonReader.Read()); // "name" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("name", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // "John" - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("John", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // "age" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("age", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // 30 - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(30L, jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // "active" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("active", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // true - Assert.AreEqual(JsonTokenType.Boolean, jsonReader.TokenType); - Assert.AreEqual(true, jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // "scores" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("scores", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // [ - Assert.AreEqual(JsonTokenType.StartArray, jsonReader.TokenType); - - Assert.IsTrue(jsonReader.Read()); // 85 - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(85L, jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // 92 - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(92L, jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // 78 - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(78L, jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // ] - Assert.AreEqual(JsonTokenType.EndArray, jsonReader.TokenType); - - Assert.IsTrue(jsonReader.Read()); // "address" - Assert.AreEqual(JsonTokenType.PropertyName, jsonReader.TokenType); - Assert.AreEqual("address", jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // null - Assert.AreEqual(JsonTokenType.Null, jsonReader.TokenType); - Assert.IsNull(jsonReader.Value); - - Assert.IsTrue(jsonReader.Read()); // } - Assert.AreEqual(JsonTokenType.EndObject, jsonReader.TokenType); - - Assert.IsFalse(jsonReader.Read()); // EOF - Assert.AreEqual(JsonTokenType.None, jsonReader.TokenType); - } - - [TestMethod] - public void Read_WithWhitespace_ShouldSkipWhitespace() - { - // Arrange - using var jsonReader = new JsonReader(" \t\r\n { \t\r\n } \t\r\n "); - - // Act & Assert - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.StartObject, jsonReader.TokenType); - - Assert.IsTrue(jsonReader.Read()); - Assert.AreEqual(JsonTokenType.EndObject, jsonReader.TokenType); - - Assert.IsFalse(jsonReader.Read()); - } - - [TestMethod] - public void LineAndColumn_ShouldUpdateCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("{\n \"name\": \"value\"\n}"); - - // Act & Assert - Assert.AreEqual(1, jsonReader.Line); - Assert.AreEqual(1, jsonReader.Column); - - Assert.IsTrue(jsonReader.Read()); // { - Assert.AreEqual(1, jsonReader.Line); - Assert.AreEqual(2, jsonReader.Column); - - Assert.IsTrue(jsonReader.Read()); // "name" - Assert.AreEqual(2, jsonReader.Line); - - Assert.IsTrue(jsonReader.Read()); // "value" - - Assert.IsTrue(jsonReader.Read()); // } - Assert.AreEqual(3, jsonReader.Line); - } - - [TestMethod] - public void Read_UnexpectedCharacter_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("@"); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_AfterDispose_ShouldThrowObjectDisposedException() - { - // Arrange - var jsonReader = new JsonReader("{}"); - jsonReader.Dispose(); - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Dispose_ShouldDisposeCorrectly() - { - // Arrange - var textReader = new StringReader("{}"); - var jsonReader = new JsonReader(textReader, ownsReader: true); - - // Act - jsonReader.Dispose(); - - // Assert - 第二次调用不应该抛出异常 - jsonReader.Dispose(); - } - - [TestMethod] - public void Dispose_WithoutOwningReader_ShouldNotDisposeReader() - { - // Arrange - var textReader = new StringReader("{}"); - var jsonReader = new JsonReader(textReader, ownsReader: false); - - // Act - jsonReader.Dispose(); - - // Assert - Reader 应该仍然可用 - // 注意:这里我们无法直接验证 TextReader 未被释放,因为 StringReader 没有公共的 IsDisposed 属性 - // 但我们可以确保 JsonReader 正确处理了 ownsReader 参数 - } - - [TestMethod] - public void Read_LargeNumber_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("9223372036854775807"); // long.MaxValue - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(long.MaxValue, jsonReader.Value); - } - - [TestMethod] - public void Read_VeryLargeNumber_ShouldReadAsDouble() - { - // Arrange - using var jsonReader = new JsonReader("1.7976931348623157e308"); // 接近 double.MaxValue - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.IsInstanceOfType(jsonReader.Value, typeof(double)); - } - - [TestMethod] - public void Read_StringWithUnescapedControlCharacter_ShouldThrowException() - { - // Arrange - using var jsonReader = new JsonReader("\"\u0001\""); // 未转义的控制字符 - - // Act & Assert - Assert.Throws(() => jsonReader.Read()); - } - - [TestMethod] - public void Read_EmptyString_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("\"\""); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.String, jsonReader.TokenType); - Assert.AreEqual("", jsonReader.Value); - } - - [TestMethod] - public void Read_Zero_ShouldReadCorrectly() - { - // Arrange - using var jsonReader = new JsonReader("0"); - - // Act - Assert.IsTrue(jsonReader.Read()); - - // Assert - Assert.AreEqual(JsonTokenType.Number, jsonReader.TokenType); - Assert.AreEqual(0L, jsonReader.Value); - } -} \ No newline at end of file diff --git a/tests/LuYao.Common.UnitTests/Text/Json/JsonWriterTests.cs b/tests/LuYao.Common.UnitTests/Text/Json/JsonWriterTests.cs deleted file mode 100644 index 3569e6a..0000000 --- a/tests/LuYao.Common.UnitTests/Text/Json/JsonWriterTests.cs +++ /dev/null @@ -1,715 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Globalization; -using System.IO; -using System.Text; - -namespace LuYao.Text.Json; - -[TestClass] -public class JsonWriterTests -{ - private StringBuilder _stringBuilder; - private JsonWriter _writer; - - [TestInitialize] - public void Setup() - { - _stringBuilder = new StringBuilder(); - _writer = new JsonWriter(_stringBuilder); - } - - [TestCleanup] - public void Cleanup() - { - _writer?.Dispose(); - } - - #region Constructor Tests - - [TestMethod] - public void Constructor_WithTextWriter_ShouldCreateWriter() - { - // Arrange - using var stringWriter = new StringWriter(); - - // Act - using var writer = new JsonWriter(stringWriter); - - // Assert - Assert.IsNotNull(writer); - } - - [TestMethod] - public void Constructor_WithStringBuilder_ShouldCreateWriter() - { - // Arrange - var sb = new StringBuilder(); - - // Act - using var writer = new JsonWriter(sb); - - // Assert - Assert.IsNotNull(writer); - } - - [TestMethod] - public void Constructor_WithNullTextWriter_ShouldThrowArgumentNullException() - { - // Arrange & Act & Assert - Assert.Throws(() => new JsonWriter((TextWriter)null)); - } - - [TestMethod] - public void Constructor_WithIndentation_ShouldCreateIndentedWriter() - { - // Arrange - var sb = new StringBuilder(); - - // Act - using var writer = new JsonWriter(sb, indented: true, indentString: " "); - - // Assert - Assert.IsNotNull(writer); - } - - #endregion - - #region Object Tests - - [TestMethod] - public void WriteStartObject_ShouldWriteOpeningBrace() - { - // Act - _writer.WriteStartObject(); - _writer.Flush(); - - // Assert - Assert.AreEqual("{", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteEndObject_ShouldWriteClosingBrace() - { - // Act - _writer.WriteStartObject(); - _writer.WriteEndObject(); - _writer.Flush(); - - // Assert - Assert.AreEqual("{}", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteObject_WithIndentation_ShouldFormatCorrectly() - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb, indented: true); - - // Act - writer.WriteStartObject(); - writer.WritePropertyName("test"); - writer.WriteValue("value"); - writer.WriteEndObject(); - writer.Flush(); - - // Assert - var result = sb.ToString(); - Assert.IsTrue(result.Contains("{\n")); - Assert.IsTrue(result.Contains(" \"test\": \"value\"\n")); - Assert.IsTrue(result.Contains("}")); - } - - [TestMethod] - public void WriteEndObject_WithoutStartObject_ShouldThrowInvalidOperationException() - { - // Act & Assert - Assert.Throws(() => _writer.WriteEndObject()); - } - - #endregion - - #region Array Tests - - [TestMethod] - public void WriteStartArray_ShouldWriteOpeningBracket() - { - // Act - _writer.WriteStartArray(); - _writer.Flush(); - - // Assert - Assert.AreEqual("[", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteEndArray_ShouldWriteClosingBracket() - { - // Act - _writer.WriteStartArray(); - _writer.WriteEndArray(); - _writer.Flush(); - - // Assert - Assert.AreEqual("[]", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteArray_WithIndentation_ShouldFormatCorrectly() - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb, indented: true); - - // Act - writer.WriteStartArray(); - writer.WriteValue(1); - writer.WriteValue(2); - writer.WriteEndArray(); - writer.Flush(); - - // Assert - var result = sb.ToString(); - Assert.IsTrue(result.Contains("[\n")); - Assert.IsTrue(result.Contains("1,\n")); - Assert.IsTrue(result.Contains("2\n")); - Assert.IsTrue(result.Contains("]")); - } - - [TestMethod] - public void WriteEndArray_WithoutStartArray_ShouldThrowInvalidOperationException() - { - // Act & Assert - Assert.Throws(() => _writer.WriteEndArray()); - } - - #endregion - - #region Property Tests - - [TestMethod] - public void WritePropertyName_ValidName_ShouldWriteQuotedName() - { - // Act - _writer.WriteStartObject(); - _writer.WritePropertyName("test"); - _writer.Flush(); - - // Assert - Assert.AreEqual("{\"test\":", _stringBuilder.ToString()); - } - - [TestMethod] - public void WritePropertyName_WithIndentation_ShouldIncludeSpace() - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb, indented: true); - - // Act - writer.WriteStartObject(); - writer.WritePropertyName("test"); - writer.Flush(); - - // Assert - var result = sb.ToString(); - Assert.IsTrue(result.Contains("\"test\": ")); - } - - [TestMethod] - public void WritePropertyName_NullName_ShouldThrowArgumentNullException() - { - // Act & Assert - Assert.Throws(() => _writer.WritePropertyName(null)); - } - - #endregion - - #region String Value Tests - - [TestMethod] - public void WriteValue_String_ShouldWriteQuotedString() - { - // Act - _writer.WriteValue("test"); - _writer.Flush(); - - // Assert - Assert.AreEqual("\"test\"", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_NullString_ShouldWriteNull() - { - // Act - _writer.WriteValue((string)null); - _writer.Flush(); - - // Assert - Assert.AreEqual("null", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_StringWithEscapeCharacters_ShouldEscapeCorrectly() - { - // Arrange - var testCases = new[] - { - ("\"", "\\\""), - ("\\", "\\\\"), - ("\b", "\\b"), - ("\f", "\\f"), - ("\n", "\\n"), - ("\r", "\\r"), - ("\t", "\\t") - }; - - foreach (var (input, expected) in testCases) - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb); - - // Act - writer.WriteValue(input); - writer.Flush(); - - // Assert - Assert.AreEqual($"\"{expected}\"", sb.ToString(), $"Failed for input: {input}"); - } - } - - [TestMethod] - public void WriteValue_StringWithControlCharacters_ShouldUnicodeEscape() - { - // Act - _writer.WriteValue("\u0001"); - _writer.Flush(); - - // Assert - Assert.AreEqual("\"\\u0001\"", _stringBuilder.ToString()); - } - - #endregion - - #region Numeric Value Tests - - [TestMethod] - public void WriteValue_Long_ShouldWriteNumber() - { - // Act - _writer.WriteValue(12345L); - _writer.Flush(); - - // Assert - Assert.AreEqual("12345", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_NegativeLong_ShouldWriteNegativeNumber() - { - // Act - _writer.WriteValue(-12345L); - _writer.Flush(); - - // Assert - Assert.AreEqual("-12345", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_Double_ShouldWriteNumber() - { - // Act - _writer.WriteValue(123.45); - _writer.Flush(); - - // Assert - Assert.AreEqual("123.45", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_DoubleNaN_ShouldWriteNull() - { - // Act - _writer.WriteValue(double.NaN); - _writer.Flush(); - - // Assert - Assert.AreEqual("null", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_DoubleInfinity_ShouldWriteNull() - { - // Act - _writer.WriteValue(double.PositiveInfinity); - _writer.Flush(); - - // Assert - Assert.AreEqual("null", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_DoubleNegativeInfinity_ShouldWriteNull() - { - // Act - _writer.WriteValue(double.NegativeInfinity); - _writer.Flush(); - - // Assert - Assert.AreEqual("null", _stringBuilder.ToString()); - } - - #endregion - - #region Boolean Value Tests - - [TestMethod] - public void WriteValue_BooleanTrue_ShouldWriteTrue() - { - // Act - _writer.WriteValue(true); - _writer.Flush(); - - // Assert - Assert.AreEqual("true", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteValue_BooleanFalse_ShouldWriteFalse() - { - // Act - _writer.WriteValue(false); - _writer.Flush(); - - // Assert - Assert.AreEqual("false", _stringBuilder.ToString()); - } - - #endregion - - #region Null Value Tests - - [TestMethod] - public void WriteNull_ShouldWriteNull() - { - // Act - _writer.WriteNull(); - _writer.Flush(); - - // Assert - Assert.AreEqual("null", _stringBuilder.ToString()); - } - - #endregion - - #region Raw Value Tests - - [TestMethod] - public void WriteRaw_ValidJson_ShouldWriteRawValue() - { - // Act - _writer.WriteRaw("{\"key\":\"value\"}"); - _writer.Flush(); - - // Assert - Assert.AreEqual("{\"key\":\"value\"}", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteRaw_NullValue_ShouldThrowArgumentNullException() - { - // Act & Assert - Assert.Throws(() => _writer.WriteRaw(null)); - } - - #endregion - - #region Complex JSON Tests - - [TestMethod] - public void WriteComplexObject_ShouldProduceValidJson() - { - // Act - _writer.WriteStartObject(); - _writer.WritePropertyName("name"); - _writer.WriteValue("John"); - _writer.WritePropertyName("age"); - _writer.WriteValue(30L); - _writer.WritePropertyName("active"); - _writer.WriteValue(true); - _writer.WritePropertyName("address"); - _writer.WriteStartObject(); - _writer.WritePropertyName("city"); - _writer.WriteValue("New York"); - _writer.WriteEndObject(); - _writer.WriteEndObject(); - _writer.Flush(); - - // Assert - var expected = "{\"name\":\"John\",\"age\":30,\"active\":true,\"address\":{\"city\":\"New York\"}}"; - Assert.AreEqual(expected, _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteComplexArray_ShouldProduceValidJson() - { - // Act - _writer.WriteStartArray(); - _writer.WriteValue("item1"); - _writer.WriteValue(42L); - _writer.WriteStartObject(); - _writer.WritePropertyName("nested"); - _writer.WriteValue("value"); - _writer.WriteEndObject(); - _writer.WriteEndArray(); - _writer.Flush(); - - // Assert - var expected = "[\"item1\",42,{\"nested\":\"value\"}]"; - Assert.AreEqual(expected, _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteNestedArraysAndObjects_ShouldProduceValidJson() - { - // Act - _writer.WriteStartObject(); - _writer.WritePropertyName("data"); - _writer.WriteStartArray(); - _writer.WriteStartObject(); - _writer.WritePropertyName("id"); - _writer.WriteValue(1L); - _writer.WritePropertyName("tags"); - _writer.WriteStartArray(); - _writer.WriteValue("tag1"); - _writer.WriteValue("tag2"); - _writer.WriteEndArray(); - _writer.WriteEndObject(); - _writer.WriteEndArray(); - _writer.WriteEndObject(); - _writer.Flush(); - - // Assert - var expected = "{\"data\":[{\"id\":1,\"tags\":[\"tag1\",\"tag2\"]}]}"; - Assert.AreEqual(expected, _stringBuilder.ToString()); - } - - #endregion - - #region Indentation Tests - - [TestMethod] - public void WriteIndentedJson_ShouldFormatWithNewlinesAndIndentation() - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb, indented: true, indentString: " "); - - // Act - writer.WriteStartObject(); - writer.WritePropertyName("name"); - writer.WriteValue("John"); - writer.WritePropertyName("items"); - writer.WriteStartArray(); - writer.WriteValue(1L); - writer.WriteValue(2L); - writer.WriteEndArray(); - writer.WriteEndObject(); - writer.Flush(); - - // Assert - var result = sb.ToString(); - Assert.IsTrue(result.Contains("{\n")); - Assert.IsTrue(result.Contains(" \"name\": \"John\",\n")); - Assert.IsTrue(result.Contains(" \"items\": [\n")); - Assert.IsTrue(result.Contains(" 1,\n")); - Assert.IsTrue(result.Contains(" 2\n")); - Assert.IsTrue(result.Contains(" ]\n")); - Assert.IsTrue(result.Contains("}")); - } - - [TestMethod] - public void WriteIndentedJson_CustomIndentString_ShouldUseCustomIndentation() - { - // Arrange - var sb = new StringBuilder(); - using var writer = new JsonWriter(sb, indented: true, indentString: "\t"); - - // Act - writer.WriteStartObject(); - writer.WritePropertyName("key"); - writer.WriteValue("value"); - writer.WriteEndObject(); - writer.Flush(); - - // Assert - var result = sb.ToString(); - Assert.IsTrue(result.Contains("\t\"key\": \"value\"")); - } - - #endregion - - #region Buffer Management Tests - - [TestMethod] - public void WriteValue_LongString_ShouldHandleBuffering() - { - // Arrange - var longString = new string('a', 10000); - - // Act - _writer.WriteValue(longString); - _writer.Flush(); - - // Assert - Assert.AreEqual($"\"{longString}\"", _stringBuilder.ToString()); - } - - [TestMethod] - public void WriteMultipleValues_ShouldHandleBufferFlushes() - { - // Act - for (int i = 0; i < 1000; i++) - { - if (i == 0) - _writer.WriteStartArray(); - - _writer.WriteValue($"item{i}"); - - if (i == 999) - _writer.WriteEndArray(); - } - _writer.Flush(); - - // Assert - var result = _stringBuilder.ToString(); - Assert.IsTrue(result.StartsWith("[")); - Assert.IsTrue(result.EndsWith("]")); - Assert.IsTrue(result.Contains("\"item0\"")); - Assert.IsTrue(result.Contains("\"item999\"")); - } - - #endregion - - #region Error Handling Tests - - [TestMethod] - public void WriteInvalidStructure_ShouldThrowInvalidOperationException() - { - // Act & Assert - _writer.WriteStartObject(); - Assert.Throws(() => _writer.WriteEndArray()); - } - - [TestMethod] - public void WritePropertyNameOutsideObject_ShouldNotThrow() - { - // This test verifies current behavior - property names can be written at root level - // Act - _writer.WritePropertyName("test"); - _writer.WriteValue("value"); - _writer.Flush(); - - // Assert - Assert.AreEqual("\"test\":\"value\"", _stringBuilder.ToString()); - } - - #endregion - - #region Disposal Tests - - [TestMethod] - public void Dispose_ShouldFlushAndCleanup() - { - // Arrange - var sb = new StringBuilder(); - var writer = new JsonWriter(sb); - - // Act - writer.WriteValue("test"); - writer.Dispose(); - - // Assert - Assert.AreEqual("\"test\"", sb.ToString()); - } - - [TestMethod] - public void Dispose_CalledTwice_ShouldNotThrow() - { - // Arrange - var sb = new StringBuilder(); - var writer = new JsonWriter(sb); - - // Act & Assert - writer.Dispose(); - writer.Dispose(); // Should not throw - } - - [TestMethod] - public void Dispose_WithOwnedWriter_ShouldDisposeUnderlyingWriter() - { - // Arrange - var stringWriter = new StringWriter(); - var writer = new JsonWriter(stringWriter, ownsWriter: true); - - // Act - writer.WriteValue("test"); - writer.Dispose(); - - // Assert - Assert.AreEqual("\"test\"", stringWriter.ToString()); - // Note: We can't easily verify that stringWriter is disposed without reflection - } - - #endregion - - #region Culture Tests - - [TestMethod] - public void WriteValue_DoubleWithDifferentCulture_ShouldUseInvariantCulture() - { - // Arrange - var originalCulture = CultureInfo.CurrentCulture; - try - { - CultureInfo.CurrentCulture = new CultureInfo("de-DE"); // Uses comma as decimal separator - - // Act - _writer.WriteValue(123.45); - _writer.Flush(); - - // Assert - Assert.AreEqual("123.45", _stringBuilder.ToString()); // Should use dot, not comma - } - finally - { - CultureInfo.CurrentCulture = originalCulture; - } - } - - [TestMethod] - public void WriteValue_LongWithDifferentCulture_ShouldUseInvariantCulture() - { - // Arrange - var originalCulture = CultureInfo.CurrentCulture; - try - { - CultureInfo.CurrentCulture = new CultureInfo("en-IN"); // Uses different number formatting - - // Act - _writer.WriteValue(1234567L); - _writer.Flush(); - - // Assert - Assert.AreEqual("1234567", _stringBuilder.ToString()); // No thousand separators - } - finally - { - CultureInfo.CurrentCulture = originalCulture; - } - } - - #endregion -} \ No newline at end of file