XFEExtension
【DLL】XFE各类拓展是一个C#的DLL库,旨在优化C#代码中常用语句的使用,并提供更简洁的访问方式,同时提供Xunit测试框架,快速搭建服务器/客户端,免费ChatGPTAPI接口,免费通讯服务器,XFE下载器,新增格式等
关注
0
Fork
0
Star
0
using System.Buffers;
using System.Text;
namespace XFEExtension.NetCore.XFETransform.Json;
internal sealed class JsonSource(string text, JsonSettings settings)
{
public string Text { get; } = text;
public JsonSettings Settings { get; } = settings;
public long ScannedCharacters => Interlocked.Read(ref _scannedCharacters);
private long _scannedCharacters;
public void RecordScan(int count)
{
if (count > 0)
Interlocked.Add(ref _scannedCharacters, count);
}
public XFEJsonException Error(string message, int position, string path, Exception? innerException = null)
{
position = Math.Clamp(position, 0, Text.Length);
var line = 1;
var column = 1;
for (var index = 0; index < position; index++)
{
if (Text[index] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
var error = new XFEJsonError
{
Message = message,
Path = path,
Position = position,
LineNumber = line,
ColumnNumber = column
};
return innerException is null ? new(error) : new(error, innerException);
}
public static XFEJsonException ConversionError(string message, string path, Exception? innerException = null)
{
var error = new XFEJsonError
{
Message = message,
Path = path,
Position = -1,
LineNumber = 0,
ColumnNumber = 0
};
return innerException is null ? new(error) : new(error, innerException);
}
}
internal static class JsonScanner
{
public static void ValidateDocument(JsonSource source, string path = "$")
{
var position = 0;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("JSON 文本不能为空。", position, path);
var start = position;
position = SkipValueCore(source, position, 0, path, out _);
source.RecordScan(position - start);
SkipWhitespace(source.Text, ref position);
if (position != source.Text.Length)
throw source.Error("根 JSON 值之后存在额外内容。", position, path);
}
public static int SkipValue(JsonSource source, int position, string path, out XFEJsonValueKind kind)
{
var original = position;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var valueStart = position;
var end = SkipValueCore(source, position, 0, path, out kind);
source.RecordScan(end - Math.Min(original, valueStart));
return end;
}
public static int SkipValueAtDepth(JsonSource source, int position, int depth, string path, out XFEJsonValueKind kind)
{
var original = position;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var end = SkipValueCore(source, position, depth, path, out kind);
source.RecordScan(end - original);
return end;
}
public static int SkipWhitespace(string text, int position)
{
SkipWhitespace(text, ref position);
return position;
}
public static XFEJsonValueKind Classify(JsonSource source, int position, string path, out int valueStart)
{
valueStart = SkipWhitespace(source.Text, position);
if (valueStart >= source.Text.Length)
throw source.Error("缺少 JSON 值。", valueStart, path);
return source.Text[valueStart] switch
{
'{' => XFEJsonValueKind.Object,
'[' => XFEJsonValueKind.Array,
'"' => XFEJsonValueKind.String,
't' => XFEJsonValueKind.True,
'f' => XFEJsonValueKind.False,
'n' => XFEJsonValueKind.Null,
'-' or >= '0' and <= '9' => XFEJsonValueKind.Number,
_ => throw source.Error($"字符“{source.Text[valueStart]}”不能作为 JSON 值的开头。", valueStart, path)
};
}
public static string ReadString(JsonSource source, int position, string path, out int end)
{
var text = source.Text;
if (position >= text.Length || text[position] != '"')
throw source.Error("应为 JSON 字符串。", position, path);
var builder = new StringBuilder();
var index = position + 1;
while (index < text.Length)
{
var current = text[index++];
if (current == '"')
{
end = index;
source.RecordScan(end - position);
return builder.ToString();
}
if (current < 0x20)
throw source.Error("JSON 字符串中包含未转义的控制字符。", index - 1, path);
if (current == '\\')
{
if (index >= text.Length)
throw source.Error("JSON 字符串的转义序列不完整。", index - 1, path);
var escaped = text[index++];
switch (escaped)
{
case '"': builder.Append('"'); break;
case '\\': builder.Append('\\'); break;
case '/': builder.Append('/'); break;
case 'b': builder.Append('\b'); break;
case 'f': builder.Append('\f'); break;
case 'n': builder.Append('\n'); break;
case 'r': builder.Append('\r'); break;
case 't': builder.Append('\t'); break;
case 'u':
{
var first = ReadHexCodeUnit(source, ref index, path);
if (char.IsHighSurrogate((char)first))
{
if (index + 1 >= text.Length || text[index] != '\\' || text[index + 1] != 'u')
throw source.Error("高代理项之后缺少低代理项。", index, path);
index += 2;
var second = ReadHexCodeUnit(source, ref index, path);
if (!char.IsLowSurrogate((char)second))
throw source.Error("高代理项之后不是合法的低代理项。", index - 4, path);
builder.Append((char)first);
builder.Append((char)second);
}
else if (char.IsLowSurrogate((char)first))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 4, path);
}
else
{
builder.Append((char)first);
}
break;
}
default:
throw source.Error($"不支持的 JSON 转义字符“\\{escaped}”。", index - 2, path);
}
continue;
}
if (char.IsHighSurrogate(current))
{
if (index >= text.Length || !char.IsLowSurrogate(text[index]))
throw source.Error("JSON 字符串包含孤立的高代理项。", index - 1, path);
builder.Append(current);
builder.Append(text[index++]);
}
else if (char.IsLowSurrogate(current))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 1, path);
}
else
{
builder.Append(current);
}
}
throw source.Error("JSON 字符串缺少结束双引号。", text.Length, path);
}
public static string AppendPropertyPath(string parent, string propertyName)
{
if (propertyName.Length > 0 && IsIdentifierStart(propertyName[0]) && propertyName.All(IsIdentifierPart))
return $"{parent}.{propertyName}";
return $"{parent}['{propertyName.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("'", "\\'", StringComparison.Ordinal)}']";
}
private static bool IsIdentifierStart(char value) => char.IsLetter(value) || value is '_' or '$';
private static bool IsIdentifierPart(char value) => char.IsLetterOrDigit(value) || value is '_' or '$';
private static int SkipValueCore(JsonSource source, int position, int depth, string path, out XFEJsonValueKind kind)
{
var text = source.Text;
if (position >= text.Length)
throw source.Error("缺少 JSON 值。", position, path);
switch (text[position])
{
case '{':
kind = XFEJsonValueKind.Object;
EnsureDepth(source, depth, position, path);
return SkipComposite(source, position, depth, path, JsonContainerType.Object);
case '[':
kind = XFEJsonValueKind.Array;
EnsureDepth(source, depth, position, path);
return SkipComposite(source, position, depth, path, JsonContainerType.Array);
case '"':
kind = XFEJsonValueKind.String;
return SkipString(source, position, path);
case 't':
kind = XFEJsonValueKind.True;
return SkipLiteral(source, position, "true", path);
case 'f':
kind = XFEJsonValueKind.False;
return SkipLiteral(source, position, "false", path);
case 'n':
kind = XFEJsonValueKind.Null;
return SkipLiteral(source, position, "null", path);
case '-':
case >= '0' and <= '9':
kind = XFEJsonValueKind.Number;
return SkipNumber(source, position, path);
default:
throw source.Error($"字符“{text[position]}”不能作为 JSON 值的开头。", position, path);
}
}
private static int SkipComposite(JsonSource source, int position, int baseDepth, string path, JsonContainerType rootType)
{
var text = source.Text;
var frames = ArrayPool<ContainerFrame>.Shared.Rent(source.Settings.MaxDepth + 1);
var frameCount = 1;
frames[0] = new ContainerFrame(rootType, 0);
var index = position + 1;
try
{
while (frameCount > 0)
{
ref var frame = ref frames[frameCount - 1];
SkipWhitespace(text, ref index);
if (frame.Type == JsonContainerType.Object)
{
switch (frame.State)
{
case 0: // First property name or end.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
{
index++;
frameCount--;
continue;
}
if (text[index] != '"')
throw source.Error("JSON 对象属性名称必须是双引号字符串。", index, path);
index = SkipString(source, index, path);
frame.State = 1;
continue;
case 1: // Colon.
if (index >= text.Length || text[index] != ':')
throw source.Error("JSON 属性名称之后缺少冒号。", index, path);
index++;
frame.State = 2;
continue;
case 2: // Property value.
frame.State = 3;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
case 3: // Comma or end.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
{
index++;
frameCount--;
continue;
}
if (text[index] != ',')
throw source.Error("JSON 对象成员之后应为逗号或结束大括号。", index, path);
index++;
frame.State = 4;
continue;
case 4: // Property name after comma; end means trailing comma.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
throw source.Error("JSON 对象不允许尾逗号。", index, path);
if (text[index] != '"')
throw source.Error("JSON 对象属性名称必须是双引号字符串。", index, path);
index = SkipString(source, index, path);
frame.State = 1;
continue;
}
}
else
{
switch (frame.State)
{
case 0: // First element or end.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
{
index++;
frameCount--;
continue;
}
frame.State = 1;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
case 1: // Comma or end.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
{
index++;
frameCount--;
continue;
}
if (text[index] != ',')
throw source.Error("JSON 数组元素之后应为逗号或结束方括号。", index, path);
index++;
frame.State = 2;
continue;
case 2: // Element after comma; end means trailing comma.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
throw source.Error("JSON 数组不允许尾逗号。", index, path);
frame.State = 1;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
}
}
throw source.Error("JSON 容器扫描器进入无效状态。", index, path);
}
return index;
}
finally
{
Array.Clear(frames, 0, frameCount);
ArrayPool<ContainerFrame>.Shared.Return(frames);
}
}
private static void ConsumeNestedValue(
JsonSource source,
ref int position,
int baseDepth,
string path,
ContainerFrame[] frames,
ref int frameCount)
{
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var current = source.Text[position];
if (current is '{' or '[')
{
EnsureDepth(source, baseDepth + frameCount, position, path);
frames[frameCount++] = new ContainerFrame(
current == '{' ? JsonContainerType.Object : JsonContainerType.Array,
0);
position++;
return;
}
position = current switch
{
'"' => SkipString(source, position, path),
't' => SkipLiteral(source, position, "true", path),
'f' => SkipLiteral(source, position, "false", path),
'n' => SkipLiteral(source, position, "null", path),
'-' or >= '0' and <= '9' => SkipNumber(source, position, path),
_ => throw source.Error($"字符“{current}”不能作为 JSON 值的开头。", position, path)
};
}
private static int SkipString(JsonSource source, int position, string path)
{
var text = source.Text;
var index = position + 1;
while (index < text.Length)
{
var current = text[index++];
if (current == '"')
return index;
if (current < 0x20)
throw source.Error("JSON 字符串中包含未转义的控制字符。", index - 1, path);
if (current == '\\')
{
if (index >= text.Length)
throw source.Error("JSON 字符串的转义序列不完整。", index - 1, path);
var escaped = text[index++];
if (escaped is '"' or '\\' or '/' or 'b' or 'f' or 'n' or 'r' or 't')
continue;
if (escaped != 'u')
throw source.Error($"不支持的 JSON 转义字符“\\{escaped}”。", index - 2, path);
var first = ReadHexCodeUnit(source, ref index, path);
if (char.IsHighSurrogate((char)first))
{
if (index + 1 >= text.Length || text[index] != '\\' || text[index + 1] != 'u')
throw source.Error("高代理项之后缺少低代理项。", index, path);
index += 2;
var second = ReadHexCodeUnit(source, ref index, path);
if (!char.IsLowSurrogate((char)second))
throw source.Error("高代理项之后不是合法的低代理项。", index - 4, path);
}
else if (char.IsLowSurrogate((char)first))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 4, path);
}
continue;
}
if (char.IsHighSurrogate(current))
{
if (index >= text.Length || !char.IsLowSurrogate(text[index]))
throw source.Error("JSON 字符串包含孤立的高代理项。", index - 1, path);
index++;
}
else if (char.IsLowSurrogate(current))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 1, path);
}
}
throw source.Error("JSON 字符串缺少结束双引号。", text.Length, path);
}
private static int ReadHexCodeUnit(JsonSource source, ref int index, string path)
{
if (index + 4 > source.Text.Length)
throw source.Error("Unicode 转义序列不足四位。", index, path);
var value = 0;
for (var count = 0; count < 4; count++)
{
var current = source.Text[index++];
value = (value << 4) | current switch
{
>= '0' and <= '9' => current - '0',
>= 'a' and <= 'f' => current - 'a' + 10,
>= 'A' and <= 'F' => current - 'A' + 10,
_ => throw source.Error("Unicode 转义序列包含非十六进制字符。", index - 1, path)
};
}
return value;
}
private static int SkipLiteral(JsonSource source, int position, string literal, string path)
{
if (position + literal.Length > source.Text.Length ||
!source.Text.AsSpan(position, literal.Length).SequenceEqual(literal.AsSpan()))
throw source.Error($"无效的 JSON 字面量,应为“{literal}”。", position, path);
return position + literal.Length;
}
private static int SkipNumber(JsonSource source, int position, string path)
{
var text = source.Text;
var index = position;
if (text[index] == '-')
{
index++;
if (index >= text.Length)
throw source.Error("负号之后缺少数字。", index, path);
}
if (text[index] == '0')
{
index++;
if (index < text.Length && char.IsAsciiDigit(text[index]))
throw source.Error("JSON 数字不允许前导零。", index, path);
}
else if (text[index] is >= '1' and <= '9')
{
do index++; while (index < text.Length && char.IsAsciiDigit(text[index]));
}
else
{
throw source.Error("JSON 数字的整数部分无效。", index, path);
}
if (index < text.Length && text[index] == '.')
{
index++;
var fractionStart = index;
while (index < text.Length && char.IsAsciiDigit(text[index])) index++;
if (index == fractionStart)
throw source.Error("小数点之后至少需要一位数字。", index, path);
}
if (index < text.Length && text[index] is 'e' or 'E')
{
index++;
if (index < text.Length && text[index] is '+' or '-') index++;
var exponentStart = index;
while (index < text.Length && char.IsAsciiDigit(text[index])) index++;
if (index == exponentStart)
throw source.Error("指数部分至少需要一位数字。", index, path);
}
return index;
}
private static void EnsureDepth(JsonSource source, int depth, int position, string path)
{
if (depth >= source.Settings.MaxDepth)
throw source.Error($"JSON 嵌套深度超过限制 {source.Settings.MaxDepth}。", position, path);
}
private static void SkipWhitespace(string text, ref int position)
{
while (position < text.Length && text[position] is ' ' or '\t' or '\r' or '\n')
position++;
}
private enum JsonContainerType : byte
{
Object,
Array
}
private struct ContainerFrame(JsonContainerType type, byte state)
{
public JsonContainerType Type = type;
public byte State = state;
}
}
using System.Buffers;
using System.Text;
namespace XFEExtension.NetCore.XFETransform.Json;
internal sealed class JsonSource(string text, JsonSettings settings)
{
public string Text { get; } = text;
public JsonSettings Settings { get; } = settings;
public long ScannedCharacters => Interlocked.Read(ref _scannedCharacters);
private long _scannedCharacters;
public void RecordScan(int count)
{
if (count > 0)
Interlocked.Add(ref _scannedCharacters, count);
}
public XFEJsonException Error(string message, int position, string path, Exception? innerException = null)
{
position = Math.Clamp(position, 0, Text.Length);
var line = 1;
var column = 1;
for (var index = 0; index < position; index++)
{
if (Text[index] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
var error = new XFEJsonError
{
Message = message,
Path = path,
Position = position,
LineNumber = line,
ColumnNumber = column
};
return innerException is null ? new(error) : new(error, innerException);
}
public static XFEJsonException ConversionError(string message, string path, Exception? innerException = null)
{
var error = new XFEJsonError
{
Message = message,
Path = path,
Position = -1,
LineNumber = 0,
ColumnNumber = 0
};
return innerException is null ? new(error) : new(error, innerException);
}
}
internal static class JsonScanner
{
public static void ValidateDocument(JsonSource source, string path = "$")
{
var position = 0;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("JSON 文本不能为空。", position, path);
var start = position;
position = SkipValueCore(source, position, 0, path, out _);
source.RecordScan(position - start);
SkipWhitespace(source.Text, ref position);
if (position != source.Text.Length)
throw source.Error("根 JSON 值之后存在额外内容。", position, path);
}
public static int SkipValue(JsonSource source, int position, string path, out XFEJsonValueKind kind)
{
var original = position;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var valueStart = position;
var end = SkipValueCore(source, position, 0, path, out kind);
source.RecordScan(end - Math.Min(original, valueStart));
return end;
}
public static int SkipValueAtDepth(JsonSource source, int position, int depth, string path, out XFEJsonValueKind kind)
{
var original = position;
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var end = SkipValueCore(source, position, depth, path, out kind);
source.RecordScan(end - original);
return end;
}
public static int SkipWhitespace(string text, int position)
{
SkipWhitespace(text, ref position);
return position;
}
public static XFEJsonValueKind Classify(JsonSource source, int position, string path, out int valueStart)
{
valueStart = SkipWhitespace(source.Text, position);
if (valueStart >= source.Text.Length)
throw source.Error("缺少 JSON 值。", valueStart, path);
return source.Text[valueStart] switch
{
'{' => XFEJsonValueKind.Object,
'[' => XFEJsonValueKind.Array,
'"' => XFEJsonValueKind.String,
't' => XFEJsonValueKind.True,
'f' => XFEJsonValueKind.False,
'n' => XFEJsonValueKind.Null,
'-' or >= '0' and <= '9' => XFEJsonValueKind.Number,
_ => throw source.Error($"字符“{source.Text[valueStart]}”不能作为 JSON 值的开头。", valueStart, path)
};
}
public static string ReadString(JsonSource source, int position, string path, out int end)
{
var text = source.Text;
if (position >= text.Length || text[position] != '"')
throw source.Error("应为 JSON 字符串。", position, path);
var builder = new StringBuilder();
var index = position + 1;
while (index < text.Length)
{
var current = text[index++];
if (current == '"')
{
end = index;
source.RecordScan(end - position);
return builder.ToString();
}
if (current < 0x20)
throw source.Error("JSON 字符串中包含未转义的控制字符。", index - 1, path);
if (current == '\\')
{
if (index >= text.Length)
throw source.Error("JSON 字符串的转义序列不完整。", index - 1, path);
var escaped = text[index++];
switch (escaped)
{
case '"': builder.Append('"'); break;
case '\\': builder.Append('\\'); break;
case '/': builder.Append('/'); break;
case 'b': builder.Append('\b'); break;
case 'f': builder.Append('\f'); break;
case 'n': builder.Append('\n'); break;
case 'r': builder.Append('\r'); break;
case 't': builder.Append('\t'); break;
case 'u':
{
var first = ReadHexCodeUnit(source, ref index, path);
if (char.IsHighSurrogate((char)first))
{
if (index + 1 >= text.Length || text[index] != '\\' || text[index + 1] != 'u')
throw source.Error("高代理项之后缺少低代理项。", index, path);
index += 2;
var second = ReadHexCodeUnit(source, ref index, path);
if (!char.IsLowSurrogate((char)second))
throw source.Error("高代理项之后不是合法的低代理项。", index - 4, path);
builder.Append((char)first);
builder.Append((char)second);
}
else if (char.IsLowSurrogate((char)first))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 4, path);
}
else
{
builder.Append((char)first);
}
break;
}
default:
throw source.Error($"不支持的 JSON 转义字符“\\{escaped}”。", index - 2, path);
}
continue;
}
if (char.IsHighSurrogate(current))
{
if (index >= text.Length || !char.IsLowSurrogate(text[index]))
throw source.Error("JSON 字符串包含孤立的高代理项。", index - 1, path);
builder.Append(current);
builder.Append(text[index++]);
}
else if (char.IsLowSurrogate(current))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 1, path);
}
else
{
builder.Append(current);
}
}
throw source.Error("JSON 字符串缺少结束双引号。", text.Length, path);
}
public static string AppendPropertyPath(string parent, string propertyName)
{
if (propertyName.Length > 0 && IsIdentifierStart(propertyName[0]) && propertyName.All(IsIdentifierPart))
return $"{parent}.{propertyName}";
return $"{parent}['{propertyName.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("'", "\\'", StringComparison.Ordinal)}']";
}
private static bool IsIdentifierStart(char value) => char.IsLetter(value) || value is '_' or '$';
private static bool IsIdentifierPart(char value) => char.IsLetterOrDigit(value) || value is '_' or '$';
private static int SkipValueCore(JsonSource source, int position, int depth, string path, out XFEJsonValueKind kind)
{
var text = source.Text;
if (position >= text.Length)
throw source.Error("缺少 JSON 值。", position, path);
switch (text[position])
{
case '{':
kind = XFEJsonValueKind.Object;
EnsureDepth(source, depth, position, path);
return SkipComposite(source, position, depth, path, JsonContainerType.Object);
case '[':
kind = XFEJsonValueKind.Array;
EnsureDepth(source, depth, position, path);
return SkipComposite(source, position, depth, path, JsonContainerType.Array);
case '"':
kind = XFEJsonValueKind.String;
return SkipString(source, position, path);
case 't':
kind = XFEJsonValueKind.True;
return SkipLiteral(source, position, "true", path);
case 'f':
kind = XFEJsonValueKind.False;
return SkipLiteral(source, position, "false", path);
case 'n':
kind = XFEJsonValueKind.Null;
return SkipLiteral(source, position, "null", path);
case '-':
case >= '0' and <= '9':
kind = XFEJsonValueKind.Number;
return SkipNumber(source, position, path);
default:
throw source.Error($"字符“{text[position]}”不能作为 JSON 值的开头。", position, path);
}
}
private static int SkipComposite(JsonSource source, int position, int baseDepth, string path, JsonContainerType rootType)
{
var text = source.Text;
var frames = ArrayPool<ContainerFrame>.Shared.Rent(source.Settings.MaxDepth + 1);
var frameCount = 1;
frames[0] = new ContainerFrame(rootType, 0);
var index = position + 1;
try
{
while (frameCount > 0)
{
ref var frame = ref frames[frameCount - 1];
SkipWhitespace(text, ref index);
if (frame.Type == JsonContainerType.Object)
{
switch (frame.State)
{
case 0: // First property name or end.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
{
index++;
frameCount--;
continue;
}
if (text[index] != '"')
throw source.Error("JSON 对象属性名称必须是双引号字符串。", index, path);
index = SkipString(source, index, path);
frame.State = 1;
continue;
case 1: // Colon.
if (index >= text.Length || text[index] != ':')
throw source.Error("JSON 属性名称之后缺少冒号。", index, path);
index++;
frame.State = 2;
continue;
case 2: // Property value.
frame.State = 3;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
case 3: // Comma or end.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
{
index++;
frameCount--;
continue;
}
if (text[index] != ',')
throw source.Error("JSON 对象成员之后应为逗号或结束大括号。", index, path);
index++;
frame.State = 4;
continue;
case 4: // Property name after comma; end means trailing comma.
if (index >= text.Length)
throw source.Error("JSON 对象缺少结束大括号。", index, path);
if (text[index] == '}')
throw source.Error("JSON 对象不允许尾逗号。", index, path);
if (text[index] != '"')
throw source.Error("JSON 对象属性名称必须是双引号字符串。", index, path);
index = SkipString(source, index, path);
frame.State = 1;
continue;
}
}
else
{
switch (frame.State)
{
case 0: // First element or end.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
{
index++;
frameCount--;
continue;
}
frame.State = 1;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
case 1: // Comma or end.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
{
index++;
frameCount--;
continue;
}
if (text[index] != ',')
throw source.Error("JSON 数组元素之后应为逗号或结束方括号。", index, path);
index++;
frame.State = 2;
continue;
case 2: // Element after comma; end means trailing comma.
if (index >= text.Length)
throw source.Error("JSON 数组缺少结束方括号。", index, path);
if (text[index] == ']')
throw source.Error("JSON 数组不允许尾逗号。", index, path);
frame.State = 1;
ConsumeNestedValue(source, ref index, baseDepth, path, frames, ref frameCount);
continue;
}
}
throw source.Error("JSON 容器扫描器进入无效状态。", index, path);
}
return index;
}
finally
{
Array.Clear(frames, 0, frameCount);
ArrayPool<ContainerFrame>.Shared.Return(frames);
}
}
private static void ConsumeNestedValue(
JsonSource source,
ref int position,
int baseDepth,
string path,
ContainerFrame[] frames,
ref int frameCount)
{
SkipWhitespace(source.Text, ref position);
if (position >= source.Text.Length)
throw source.Error("缺少 JSON 值。", position, path);
var current = source.Text[position];
if (current is '{' or '[')
{
EnsureDepth(source, baseDepth + frameCount, position, path);
frames[frameCount++] = new ContainerFrame(
current == '{' ? JsonContainerType.Object : JsonContainerType.Array,
0);
position++;
return;
}
position = current switch
{
'"' => SkipString(source, position, path),
't' => SkipLiteral(source, position, "true", path),
'f' => SkipLiteral(source, position, "false", path),
'n' => SkipLiteral(source, position, "null", path),
'-' or >= '0' and <= '9' => SkipNumber(source, position, path),
_ => throw source.Error($"字符“{current}”不能作为 JSON 值的开头。", position, path)
};
}
private static int SkipString(JsonSource source, int position, string path)
{
var text = source.Text;
var index = position + 1;
while (index < text.Length)
{
var current = text[index++];
if (current == '"')
return index;
if (current < 0x20)
throw source.Error("JSON 字符串中包含未转义的控制字符。", index - 1, path);
if (current == '\\')
{
if (index >= text.Length)
throw source.Error("JSON 字符串的转义序列不完整。", index - 1, path);
var escaped = text[index++];
if (escaped is '"' or '\\' or '/' or 'b' or 'f' or 'n' or 'r' or 't')
continue;
if (escaped != 'u')
throw source.Error($"不支持的 JSON 转义字符“\\{escaped}”。", index - 2, path);
var first = ReadHexCodeUnit(source, ref index, path);
if (char.IsHighSurrogate((char)first))
{
if (index + 1 >= text.Length || text[index] != '\\' || text[index + 1] != 'u')
throw source.Error("高代理项之后缺少低代理项。", index, path);
index += 2;
var second = ReadHexCodeUnit(source, ref index, path);
if (!char.IsLowSurrogate((char)second))
throw source.Error("高代理项之后不是合法的低代理项。", index - 4, path);
}
else if (char.IsLowSurrogate((char)first))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 4, path);
}
continue;
}
if (char.IsHighSurrogate(current))
{
if (index >= text.Length || !char.IsLowSurrogate(text[index]))
throw source.Error("JSON 字符串包含孤立的高代理项。", index - 1, path);
index++;
}
else if (char.IsLowSurrogate(current))
{
throw source.Error("JSON 字符串包含孤立的低代理项。", index - 1, path);
}
}
throw source.Error("JSON 字符串缺少结束双引号。", text.Length, path);
}
private static int ReadHexCodeUnit(JsonSource source, ref int index, string path)
{
if (index + 4 > source.Text.Length)
throw source.Error("Unicode 转义序列不足四位。", index, path);
var value = 0;
for (var count = 0; count < 4; count++)
{
var current = source.Text[index++];
value = (value << 4) | current switch
{
>= '0' and <= '9' => current - '0',
>= 'a' and <= 'f' => current - 'a' + 10,
>= 'A' and <= 'F' => current - 'A' + 10,
_ => throw source.Error("Unicode 转义序列包含非十六进制字符。", index - 1, path)
};
}
return value;
}
private static int SkipLiteral(JsonSource source, int position, string literal, string path)
{
if (position + literal.Length > source.Text.Length ||
!source.Text.AsSpan(position, literal.Length).SequenceEqual(literal.AsSpan()))
throw source.Error($"无效的 JSON 字面量,应为“{literal}”。", position, path);
return position + literal.Length;
}
private static int SkipNumber(JsonSource source, int position, string path)
{
var text = source.Text;
var index = position;
if (text[index] == '-')
{
index++;
if (index >= text.Length)
throw source.Error("负号之后缺少数字。", index, path);
}
if (text[index] == '0')
{
index++;
if (index < text.Length && char.IsAsciiDigit(text[index]))
throw source.Error("JSON 数字不允许前导零。", index, path);
}
else if (text[index] is >= '1' and <= '9')
{
do index++; while (index < text.Length && char.IsAsciiDigit(text[index]));
}
else
{
throw source.Error("JSON 数字的整数部分无效。", index, path);
}
if (index < text.Length && text[index] == '.')
{
index++;
var fractionStart = index;
while (index < text.Length && char.IsAsciiDigit(text[index])) index++;
if (index == fractionStart)
throw source.Error("小数点之后至少需要一位数字。", index, path);
}
if (index < text.Length && text[index] is 'e' or 'E')
{
index++;
if (index < text.Length && text[index] is '+' or '-') index++;
var exponentStart = index;
while (index < text.Length && char.IsAsciiDigit(text[index])) index++;
if (index == exponentStart)
throw source.Error("指数部分至少需要一位数字。", index, path);
}
return index;
}
private static void EnsureDepth(JsonSource source, int depth, int position, string path)
{
if (depth >= source.Settings.MaxDepth)
throw source.Error($"JSON 嵌套深度超过限制 {source.Settings.MaxDepth}。", position, path);
}
private static void SkipWhitespace(string text, ref int position)
{
while (position < text.Length && text[position] is ' ' or '\t' or '\r' or '\n')
position++;
}
private enum JsonContainerType : byte
{
Object,
Array
}
private struct ContainerFrame(JsonContainerType type, byte state)
{
public JsonContainerType Type = type;
public byte State = state;
}
}