JsonConverter Keyvalues with = instead of : separa

2020-03-31 04:42发布

Usually I log my objects data through Newtonsoft JsonConvert.SerializeObject() which serializes to a string the object's contents.

But instead of having KeyValue pairs separated by : I want them to be separated by = to be logged by Splunk.

So instead of having like

{ "Name": "John", Age : 18 } 

I want

{ "Name" = "John", Age = 18 }

Of course I am also looking for something that should work for objects inside objects.. From the API seems a bit rigid to format the separator differently than : because it's not a JSON format at the end of the day.

There is any easy way to do this instead of reinvent the wheel?

1条回答
对你真心纯属浪费
2楼-- · 2020-03-31 05:07

This should be doable with Json.NET, but not with with a custom JsonConverter. Newtonsoft maintains a distinction between its serializer, which recursively walks a c# object graph emitting abstract lists and objects containing key/value pairs, and its writer, which formats and outputs the results of serialization according to some particular standard. You can take advantage of this distinction to create your own custom subclass of JsonWriter that formats your keys and values in Splunk's desired manner.

That being said, there's an inconsistency in your question: you quote the "Name" property name, but not the Age property name. If you want to have special rules where certain property names are quoted but others are not, I can't see a straightforward way to do that with Json.NET. However, in Logging best practices Splunk writes:

One of the most powerful features of Splunk software is its ability to extract fields from events when you search, creating structure out of unstructured data. To make sure field extraction works as intended, use the following string syntax (using spaces and commas is fine):

key1=value1, key2=value2, key3=value3 . . .

If your values contain spaces, wrap them in quotes (for example, username="bob smith").

Thus it would appear that selective quoting of property names is not needed.

Assuming that is the case, when creating a custom JsonWriter, you can use the following writers as models, both created by James Newton-King of Newtonsoft:

Based on these two classes I whipped up a partial implementation of SplunkLogTextWriter that generates output in your required format:

public class SplunkLogTextWriter : JsonWriter
{
    readonly TextWriter _writer;

    public bool QuotePropertyNames { get; set; }

    public SplunkLogTextWriter(TextWriter textWriter)
    {
        if (textWriter == null)
        {
            throw new ArgumentNullException();
        }

        _writer = textWriter;
    }

    public override void Flush()
    {
        _writer.Flush();
    }

    public override void Close()
    {
        base.Close();
        if (CloseOutput)
            _writer.Close();
    }

    public override void WriteComment(string text)
    {
        base.WriteComment(text);
        throw new NotImplementedException();
    }

    public override void WriteRaw(string json)
    {
        base.WriteRaw(json);

        _writer.Write(json);
    }

    public override void WriteStartArray()
    {
        base.WriteStartArray();
        _writer.Write("[ ");
    }

    public override void WriteStartObject()
    {
        base.WriteStartObject();
        _writer.Write("{ ");
    }

    public override void WriteStartConstructor(string name)
    {
        base.WriteStartConstructor(name);
        throw new NotImplementedException();
    }

    protected override void WriteEnd(JsonToken token)
    {
        base.WriteEnd(token);
        switch (token)
        {
            case JsonToken.EndObject:
                _writer.Write(" }");
                break;
            case JsonToken.EndArray:
                _writer.Write(" ]");
                break;
            case JsonToken.EndConstructor:
                _writer.Write(" )");
                break;
            default:
                throw new JsonWriterException("Invalid JsonToken: " + token);
        }
    }

    public override void WritePropertyName(string name)
    {
        base.WritePropertyName(name);

        WriteEscapedString(name, QuotePropertyNames);

        _writer.Write(" = ");
    }

    void WriteEscapedString(string s, bool quote)
    {
        s = s ?? string.Empty;
        quote = quote || s.Length == 0 || s.Any(c => Char.IsWhiteSpace(c) || c == '=' || c == ']' || c == '}');

        if (quote)
            _writer.Write('"');

        foreach (var c in s)
        {
            switch (c)
            {
                case '\t':
                    _writer.Write(@"\t");
                    break;
                case '\n':
                    _writer.Write(@"\n");
                    break;
                case '\r':
                    _writer.Write(@"\r");
                    break;
                case '\f':
                    _writer.Write(@"\f");
                    break;
                case '\b':
                    _writer.Write(@"\b");
                    break;
                case '\\':
                    _writer.Write(@"\\");
                    break;
                case '\u0085': // Next Line
                    _writer.Write(@"\u0085");
                    break;
                case '\u2028': // Line Separator
                    _writer.Write(@"\u2028");
                    break;
                case '\u2029': // Paragraph Separator
                    _writer.Write(@"\u2029");
                    break;
                default:
                    _writer.Write(c);
                    break;
            }
        }

        if (quote)
            _writer.Write('"');
    }

    #region WriteValue methods

    public override void WriteNull()
    {
        base.WriteNull();
        _writer.Write("null");
    }

    public override void WriteValue(bool value)
    {
        base.WriteValue(value);
        _writer.Write(value ? "true" : "false");
    }

    public override void WriteValue(byte value)
    {
        base.WriteValue(value);
        WriteIntegerValue((ulong)value);
    }

    public override void WriteValue(byte[] value)
    {
        base.WriteValue(value);
        if (value == null)
            _writer.Write("null");
        else
            WriteEscapedString(Convert.ToBase64String(value), false);
    }

    public override void WriteValue(sbyte value)
    {
        base.WriteValue(value);
        WriteIntegerValue(value);
    }

    public override void WriteValue(char value)
    {
        base.WriteValue(value);
        WriteEscapedString(value.ToString(), false);
    }

    public override void WriteValue(short value)
    {
        base.WriteValue(value);
        WriteIntegerValue(value);
    }

    public override void WriteValue(ushort value)
    {
        base.WriteValue(value);
        WriteIntegerValue((ulong)value);
    }

    public override void WriteValue(int value)
    {
        base.WriteValue(value);
        WriteIntegerValue(value);
    }

    public override void WriteValue(uint value)
    {
        base.WriteValue(value);
        WriteIntegerValue((ulong)value);
    }

    public override void WriteValue(long value)
    {
        base.WriteValue(value);
        WriteIntegerValue(value);
    }

    public override void WriteValue(ulong value)
    {
        base.WriteValue(value);
        WriteIntegerValue(value);
    }

    private void WriteIntegerValue(long value)
    {
        _writer.Write(value.ToString(Culture ?? CultureInfo.InvariantCulture));
    }

    private void WriteIntegerValue(ulong value)
    {
        _writer.Write(value.ToString(Culture ?? CultureInfo.InvariantCulture));
    }

    public override void WriteValue(DateTime value)
    {
        base.WriteValue(value);

        // Use Json.NET to format the date.  Consider replacing with something more performant.
        var s = JsonConvert.ToString(value, DateFormatHandling, DateTimeZoneHandling);
        WriteEscapedString(s.Substring(1, s.Length - 2), false);
    }

    public override void WriteValue(DateTimeOffset value)
    {
        base.WriteValue(value);

        // Use Json.NET to format the date.  Consider replacing with something more performant.
        var s = JsonConvert.ToString(value, DateFormatHandling);
        WriteEscapedString(s.Substring(1, s.Length - 2), false);
    }

    public override void WriteValue(decimal value)
    {
        base.WriteValue(value);
        _writer.Write(value.ToString(Culture ?? CultureInfo.InvariantCulture));
    }

    public override void WriteValue(double value)
    {
        base.WriteValue(value);
        // JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false) -- not public
        _writer.Write(JsonConvert.ToString(value));
    }

    public override void WriteValue(string value)
    {
        base.WriteValue(value);
        WriteEscapedString(value, false);
    }

    protected override void WriteValueDelimiter()
    {
        base.WriteValueDelimiter();
        _writer.Write(", ");
    }

    public override void WriteUndefined()
    {
        base.WriteUndefined();
        throw new NotImplementedException();
    }

    public override void WriteValue(Guid value)
    {
        base.WriteValue(value);
        throw new NotImplementedException();
    }

    #endregion

    // ToDo
    // WriteIndentSpace()
    // WriteIndent()
    // async methods
    // Others for which I threw a NotImplementedException()
}

Then if you use it as follows:

var root = new { Name = "John", Age = 18 };

var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
using (var writer = new SplunkLogTextWriter(sw))
{
    JsonSerializer.CreateDefault().Serialize(writer, root);
}

Console.WriteLine(sb);

The following result is generated:

{ Name = John, Age = 18 }

Note that some portions of the writer are not implemented yet. Look for ToDo comments and NotImplementedException throws. You will also need to tweak the writer given the formal definition of the logging format, which your question does not provide.

Sample fiddle.

查看更多
登录 后发表回答