'truncating an existing JSON string at a given level of depth - Performance optimization

i am currently searching for a way to truncate a JSON string in order to improve logging performance and file size. My plan is truncating the JSON string at a certain level of depth by appending "..." instead of actual data to a StringBuilder, so for example

{
  "data":{
    "1":"test",
    "2":"test"
  }
}

will become:

{
  "data":{...}
}

The JSON payload i am trying to log has a length of around 20000 characters. The function i have already written truncates it to around 1000, which is fine for me. Here's the code:

int c = 0;
int max = 3; //in the actual code, this is configurable of course.
bool locked = false;
char current;
bool inc = true;
bool insideString = false;
bool escaped = false;
for (int i = 0; i < text.Length; i++)
//foreach(char current in text) does not seem to improve performance in any meaningful way
{
  inc = false;
  current = text[i];
  if (current == '{') inc = true;
  else if (current == '}') c--;
  if (c < max)
  {
    //handle braces inside strings as suggested by Jeremy Lakeman
    if (!escaped && current == '\"') insideString = !insideString; 
    else if (current == '\\') escaped = true;
    builder.Append(current);
    locked = false;
  }
  else
  {
    if (!locked)
    {
      builder.Append("...");
      locked = true;
    }
  }
  if (inc) c++;
}

I have kept the variables outside of the loop in order to avoid any performance overhead that could be created through variable initialization.

Do you think there any way this code's performance could be improved any further? I'm looking forward to hearing your opinions!

Edit: I've created two methods for benchmarking, one using substrings, one using pure character parsing. Here are the results:

[Benchmark]
public void Variant1()
{
    var builder = new StringBuilder();
    var c = 0;
    char current;
    int max = 3;
    bool insideString = false;
    int next;
    for (int i = 0; i < text.Length; i++)
    {
        current = text[i];

        if (current == '\"' && text[i - 1] != '\\')
        {
            insideString = !insideString;
        }

        if (!insideString && (current == '{' || current == '}'))
        {
            int mod = 124 - current;
            c += mod;
            if (c == max - 1 && mod == -1)
            {
                builder.Append("{...");
                continue;
            }
        }

        if (c >= max)
        {
            next = text.IndexOfAny(chars, i + 1);
            if (next >= 0) i = next - 1;
            continue;
        }
        builder.Append(current);
    }
}

[Benchmark]
public void Variant2()
{
    var builder = new StringBuilder();
    var c = 0;
    char current;
    int max = 3;
    bool insideString = false;
    int next = -1;
    int prev = 0;
    while ((next = text.IndexOfAny(chars, next + 1)) >= 0)
    {
        current = text[next];
        if (current == '\"' && text[next - 1] != '\\')
        {
            insideString = !insideString;
        }

        if (c < max) builder.Append(text[prev..next]);
        if (!insideString && (current == '{' || current == '}'))
        {
            int mod = 124 - current;
            c += mod;
            if (c == max && mod == 1)
            {
                builder.Append("{...");
                continue;
            }
        }
        prev = next;
    }
}
|   Method |     Mean |    Error |   StdDev |
|--------- |---------:|---------:|---------:|
| Variant1 | 30.94 us | 0.443 us | 0.392 us |
| Variant2 | 30.16 us | 0.582 us | 0.454 us |

as you can see, both methods are nearly identical in speed.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source