The suggested answer of the custom reader wasn't working for me because the problem existed in the base reader: it just doesn't like certain trailing characters.
Since I still wanted to rely on JsonDocument.Parse()
to extract the element for me, I really just needed to find where the element stopped break that bit off as a separate piece and submit that to the parse method. Here's what I came up with:
public static bool TryParseJsonElement(this ReadOnlySpan<char> span, ref int i, out JsonElement element)
{
try
{
int end = i;
char endChar;
switch (span[i])
{
case 'f':
end += 5;
break;
case 't':
case 'n':
end += 4;
break;
case '.': case '-': case '0':
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
end = i;
var allowDash = false;
while (end < span.Length && (span[end].In('0'..'9') ||
span[end].In('e', '.', '-')))
{
if (!allowDash && span[end] == '-') break;
allowDash = span[end] == 'e';
end++;
}
break;
case '\'':
case '"':
end = i + 1;
endChar = span[i];
while (end < span.Length && span[end] != endChar)
{
if (span[end] == '\\')
{
end++;
if (end >= span.Length) break;
}
end++;
}
end++;
break;
case '{':
case '[':
end = i + 1;
endChar = span[i] == '{' ? '}' : ']';
var inString = false;
while (end < span.Length)
{
var escaped = false;
if (span[end] == '\\')
{
escaped = true;
end++;
if (end >= span.Length) break;
}
if (!escaped && span[end] == '"')
{
inString = !inString;
}
else if (!inString && span[end] == endChar) break;
end++;
}
end++;
break;
default:
element = default;
return false;
}
var block = span[i..end];
if (block[0] == '\'' && block[^1] == '\'')
block = $"\"{block[1..^1].ToString()}\"".AsSpan();
element = JsonDocument.Parse(block.ToString()).RootElement;
i = end;
return true;
}
catch
{
element = default;
return false;
}
}
It doesn't care so much about what's in the middle except (for strings, objects, and arrays) to know whether it's in the middle of a string (where it would be valid for the end character to be found) and checking for \
-delimited characters. It works well enough for my purposes.
It takes a ReadOnlySpan<char>
and an integer by reference. i
needs to be the start of the expected JSON value, and it will be advanced to the next character after, if a valid value is found. It also follows the standard Try*
pattern of returning a bool
with an output parameter for the value.