2
\$\begingroup\$

I have a method that takes a JSON string as input and "fixes" it. By that, I mean, that the JSON string comes in with all values quoted. My method un-quotes the values that shouldn't be quoted (i.e. true, false, numbers, {}, etc). The method was originally doing it by doing 4 string replaces (for the true / false, etc) and regex replace for the numbers. Kind of slow. So I re-wrote it with StringBuilder:

    protected static string FixupJson(string json)
    {
        if (json == null)
            throw new ArgumentNullException(nameof(json));

        int nLen = json.Length;

        StringBuilder sb = new StringBuilder(nLen);

        for (int i = 0; i < nLen; i++)
        {
            char ch = json[i];

            if (ch == ':' && json[i+1] == ' ' && json[i + 2] == '\"')
            {
                int k = json.IndexOf('\"', i + 3);

                sb.Append(": ");

                string g = json.Substring(i + 3, k - i - 3);

                bool bQuote = true;

                if (g == "true" || g == "false" || g == "null" || g == "{}")
                    bQuote = false;
                else if (g == "NaN")
                    bQuote = true;
                else if (Double.TryParse(g, out double d))
                    bQuote = false;

                if (bQuote)
                    sb.Append('\"');

                sb.Append(g);

                if (bQuote)
                    sb.Append('\"');

                i = k;
            }
            else
            {
                sb.Append(ch);
            }
        }

        return sb.ToString();
    }

This got me a 35% improvement, but I want to see if there is a better approach or if I can get this faster. My first StringBuilder approach, instead of appending char by char would append chunks pulled out with SubString(). That was actually a lot slower then this version.

Yes, I understand the right way to do it would be to fix it at the source :), which I do control, but the way the json gets serialized out at the source, I don't know if the value needs to be quoted until the json is fully spit out (or at least until the value is fully spit out) as a single value can be a formatted string from multiple sources (i.e. "myVal": "(this part from one place)|(this part from another place))". So "fixing it there" would mean going from a single String.Format() for the entire json to a bunch of them for each value. Right now the JSON is "pre-rendered" (once) and passed into a String.Format() to fill in the values, so by fixing it there I'd have to go to a bunch of little String.Format()'s to format each value then pass it in to the big String.Format()… so I figure this is a decent compromise.

EDIT: Should handle any old json, but this was the one I was using in my test app in C# string format ready to paste :).

"{\r\n \"xxx\": \"TRACE|1\",\r\n \"Timestamp\": \"2019-01-01 12:44:36.529\",\r\n \"Message\": {\r\n \"a\": \"bbb\",\r\n \"b1\": {\r\n \"b2\": \"test2\",\r\n \"b3\": \"Test1\",\r\n \"b4\": \"true\",\r\n \"b5\": \"178\"\r\n }\r\n },\r\n \"ThreadId\": \"1\",\r\n \"Exception\": \"{}\"\r\n}"

\$\endgroup\$
  • \$\begingroup\$ What would the typical json string look like? What does the format method look like? More eyes on it might see an even better solution. \$\endgroup\$ – tinstaafl Jan 2 at 1:28
  • \$\begingroup\$ @tinstaafl I added the one I am testing with. Basically just any old json. In this case, it should unquote the true, 178, 1 and {}. The json is generated dynamically at runtime based on a user template. My original design for performance was to parse the template once and build a "shell" string I could pass into String.Format() with as much of it pre-formatted as possible. For the above example it would start off as "{\r\n \"xxx\": \"{0}|{1}... \$\endgroup\$ – SledgeHammer Jan 2 at 2:44
  • \$\begingroup\$ @tinstaafl make sense? Since the template is used over and over, I just pass in the cached string and the params to String.Format()… the problem with that is that for example, I don't know what the {0}|{1} is going to resolve to until the StringFormat is done, so I don't know if I need to quote it. Obviously in that case I would because of the | character :)… but it could just as easily be just {0} and then I wouldn't know what it is... so essentially I need to take the whole value (could be {0}{1} as a better example) at once to decide if I want to quote it. \$\endgroup\$ – SledgeHammer Jan 2 at 2:47
  • 2
    \$\begingroup\$ This is a typical XY problem. I think you should have shown us how you create json first instead of trying to fix the serializer bugs later ;-] \$\endgroup\$ – t3chb0t Jan 2 at 6:57
1
\$\begingroup\$

I find your method for fixing invalid JSON is the worst possible because it treats JSON as a pure string and not a tree of properties/keys and values. This increases the possibility of changing values that might not be values at all. IMO the optimal approach would be to parse it with JSON.NET by using JToken.Parse and recreate the resulting tree with correct values.

You can take a look how it could be done here where I'm using the Visitor Pattern to transform JSON too.


But honestly, unless you need to be able to read some old data, you should really fix the serializer so that it outputs valid JSON instead of trying to fix its bugs with even more dirty workarounds.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.