diff --git a/src/Serilog.Formatting.Compact.Reader/LogEventReader.cs b/src/Serilog.Formatting.Compact.Reader/LogEventReader.cs
index 5f3be60..a955bf5 100644
--- a/src/Serilog.Formatting.Compact.Reader/LogEventReader.cs
+++ b/src/Serilog.Formatting.Compact.Reader/LogEventReader.cs
@@ -15,8 +15,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using System.IO;
+using System.Text.Json;
using Serilog.Events;
using Serilog.Parsing;
@@ -34,7 +34,6 @@ public class LogEventReader : IDisposable
static readonly MessageTemplateParser Parser = new();
static readonly Rendering[] NoRenderings = [];
readonly TextReader _text;
- readonly JsonSerializer _serializer;
int _lineNumber;
@@ -42,11 +41,9 @@ public class LogEventReader : IDisposable
/// Construct a .
///
/// Text to read from.
- /// If specified, a JSON serializer used when converting event documents.
- public LogEventReader(TextReader text, JsonSerializer? serializer = null)
+ public LogEventReader(TextReader text)
{
_text = text ?? throw new ArgumentNullException(nameof(text));
- _serializer = serializer ?? CreateSerializer();
}
///
@@ -127,63 +124,58 @@ public bool TryRead([NotNullWhen(true)] out LogEvent? evt)
/// Read a single log event from a JSON-encoded document.
///
/// The event in compact-JSON.
- /// If specified, a JSON serializer used when converting event documents.
/// The log event.
/// The data format is invalid.
- public static LogEvent ReadFromString(string document, JsonSerializer? serializer = null)
+ public static LogEvent ReadFromString(string document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
-
- serializer ??= CreateSerializer();
- object? result;
+ JsonDocument? data = null;
try
{
- using var reader = new JsonTextReader(new StringReader(document));
- result = serializer.Deserialize(reader);
+ data = JsonDocument.Parse(document);
}
- catch (Exception ex)
+ catch (JsonException ex)
{
throw new InvalidDataException("The document could not be deserialized.", ex);
}
-
- if (result is not JObject jObject)
- throw new InvalidDataException("The document is not a complete JSON object.");
-
- return ReadFromJObject(jObject);
+ using (data)
+ {
+ if (data == null || data.RootElement.ValueKind != JsonValueKind.Object)
+ throw new InvalidDataException($"The document is not a complete JSON object.");
+ return ReadFromJObject(data.RootElement);
+ }
}
-
///
/// Read a single log event from an already-deserialized JSON object.
///
/// The deserialized compact-JSON event.
/// The log event.
/// The data format is invalid.
- public static LogEvent ReadFromJObject(JObject jObject)
+ public static LogEvent ReadFromJObject(in JsonElement jObject)
{
- if (jObject == null) throw new ArgumentNullException(nameof(jObject));
+ if (jObject.ValueKind != JsonValueKind.Object) throw new ArgumentException(nameof(jObject));
return ReadFromJObject(1, jObject);
}
LogEvent ParseLine(string line)
{
- object? data;
+ JsonDocument? data = null;
try
{
- using var reader = new JsonTextReader(new StringReader(line));
- data = _serializer.Deserialize(reader);
+ data = JsonDocument.Parse(line);
}
- catch (Exception ex)
+ catch (JsonException)
{
- throw new InvalidDataException($"The data on line {_lineNumber} could not be deserialized.", ex);
}
-
- if (data is not JObject fields)
- throw new InvalidDataException($"The data on line {_lineNumber} is not a complete JSON object.");
-
- return ReadFromJObject(_lineNumber, fields);
+ using (data)
+ {
+ if (data == null || data.RootElement.ValueKind != JsonValueKind.Object)
+ throw new InvalidDataException($"The data on line {_lineNumber} is not a complete JSON object.");
+ return ReadFromJObject(_lineNumber, data.RootElement);
+ }
}
- static LogEvent ReadFromJObject(int lineNumber, JObject jObject)
+ static LogEvent ReadFromJObject(int lineNumber, in JsonElement jObject)
{
var timestamp = GetRequiredTimestampField(lineNumber, jObject, ClefFields.Timestamp);
@@ -206,31 +198,31 @@ static LogEvent ReadFromJObject(int lineNumber, JObject jObject)
ActivityTraceId traceId = default;
if (TryGetOptionalField(lineNumber, jObject, ClefFields.TraceId, out var tr))
traceId = ActivityTraceId.CreateFromString(tr.AsSpan());
-
+
ActivitySpanId spanId = default;
if (TryGetOptionalField(lineNumber, jObject, ClefFields.SpanId, out var sp))
spanId = ActivitySpanId.CreateFromString(sp.AsSpan());
-
+
var parsedTemplate = messageTemplate == null ?
new MessageTemplate([]) :
Parser.Parse(messageTemplate);
var renderings = NoRenderings;
-
- if (jObject.TryGetValue(ClefFields.Renderings, out var r))
+
+ if (jObject.TryGetProperty(ClefFields.Renderings, out var r))
{
- if (r is not JArray renderedByIndex)
+ if (!(r.ValueKind == JsonValueKind.Array))
throw new InvalidDataException($"The `{ClefFields.Renderings}` value on line {lineNumber} is not an array as expected.");
renderings = parsedTemplate.Tokens
.OfType()
.Where(t => t.Format != null)
- .Zip(renderedByIndex, (t, rd) => new Rendering(t.PropertyName, t.Format!, rd.Value()!))
+ .Zip(r.EnumerateArray(), (t, rd) => new Rendering(t.PropertyName, t.Format!, rd.GetString()!))
.ToArray();
}
var properties = jObject
- .Properties()
+ .EnumerateObject()
.Where(f => !ClefFields.All.Contains(f.Name))
.Select(f =>
{
@@ -248,75 +240,59 @@ static LogEvent ReadFromJObject(int lineNumber, JObject jObject)
return new LogEvent(timestamp, level, exception, parsedTemplate, properties, traceId, spanId);
}
- static bool TryGetOptionalField(int lineNumber, JObject data, string field, [NotNullWhen(true)] out string? value)
+ static bool TryGetOptionalField(int lineNumber, in JsonElement data, string field, [NotNullWhen(true)] out string? value)
{
- if (!data.TryGetValue(field, out var token) || token.Type == JTokenType.Null)
+ if (!data.TryGetProperty(field, out var prop) || prop.ValueKind == JsonValueKind.Null)
{
value = null;
return false;
}
- if (token.Type != JTokenType.String)
+ if (prop.ValueKind != JsonValueKind.String)
throw new InvalidDataException($"The value of `{field}` on line {lineNumber} is not in a supported format.");
- value = token.Value()!;
+ value = prop.GetString()!;
return true;
}
- static bool TryGetOptionalEventId(int lineNumber, JObject data, string field, out object? eventId)
+ static bool TryGetOptionalEventId(int lineNumber, in JsonElement data, string field, out object? eventId)
{
- if (!data.TryGetValue(field, out var token) || token.Type == JTokenType.Null)
+ if (!data.TryGetProperty(field, out var prop) || prop.ValueKind == JsonValueKind.Null)
{
eventId = null;
return false;
}
- switch (token.Type)
+ switch (prop.ValueKind)
{
- case JTokenType.String:
- eventId = token.Value();
- return true;
- case JTokenType.Integer:
- eventId = token.Value();
+ case JsonValueKind.String:
+ eventId = prop.GetString();
return true;
- default:
- throw new InvalidDataException(
- $"The value of `{field}` on line {lineNumber} is not in a supported format.");
+ case JsonValueKind.Number:
+ if (prop.TryGetUInt32(out var v))
+ {
+ eventId = v;
+ return true;
+ }
+ break;
}
+
+ throw new InvalidDataException(
+ $"The value of `{field}` on line {lineNumber} is not in a supported format.");
}
- static DateTimeOffset GetRequiredTimestampField(int lineNumber, JObject data, string field)
+ static DateTimeOffset GetRequiredTimestampField(int lineNumber, in JsonElement data, string field)
{
- if (!data.TryGetValue(field, out var token) || token.Type == JTokenType.Null)
+ if (!data.TryGetProperty(field, out var prop) || prop.ValueKind == JsonValueKind.Null)
throw new InvalidDataException($"The data on line {lineNumber} does not include the required `{field}` field.");
- if (token.Type == JTokenType.Date)
- {
- var dt = token.Value()!.Value;
- if (dt is DateTimeOffset offset)
- return offset;
-
- return (DateTime)dt!;
- }
- else
- {
- if (token.Type != JTokenType.String)
- throw new InvalidDataException($"The value of `{field}` on line {lineNumber} is not in a supported format.");
-
- var text = token.Value()!;
- if (!DateTimeOffset.TryParse(text, out var offset))
- throw new InvalidDataException($"The value of `{field}` on line {lineNumber} is not in a supported timestamp format.");
+ if (prop.ValueKind != JsonValueKind.String)
+ throw new InvalidDataException($"The value of `{field}` on line {lineNumber} is not in a supported format.");
- return offset;
- }
- }
+ var text = prop.GetString()!;
+ if (!DateTimeOffset.TryParse(text, out var offset))
+ throw new InvalidDataException($"The value of `{field}` on line {lineNumber} is not in a supported timestamp format.");
- static JsonSerializer CreateSerializer()
- {
- return JsonSerializer.Create(new JsonSerializerSettings
- {
- DateParseHandling = DateParseHandling.None,
- Culture = CultureInfo.InvariantCulture
- });
+ return offset;
}
}
diff --git a/src/Serilog.Formatting.Compact.Reader/PropertyFactory.cs b/src/Serilog.Formatting.Compact.Reader/PropertyFactory.cs
index 03ad63b..3d52f3e 100644
--- a/src/Serilog.Formatting.Compact.Reader/PropertyFactory.cs
+++ b/src/Serilog.Formatting.Compact.Reader/PropertyFactory.cs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using Newtonsoft.Json.Linq;
+using System.Text.Json;
using Serilog.Events;
namespace Serilog.Formatting.Compact.Reader;
@@ -22,7 +22,7 @@ static class PropertyFactory
const string TypeTagPropertyName = "$type";
const string InvalidPropertyNameSubstitute = "(unnamed)";
- public static LogEventProperty CreateProperty(string name, JToken value, Rendering[]? renderings)
+ public static LogEventProperty CreateProperty(string name, in JsonElement value, Rendering[]? renderings)
{
// The format allows (does not disallow) empty/null property names, but Serilog cannot represent them.
if (!LogEventProperty.IsValidName(name))
@@ -31,27 +31,65 @@ public static LogEventProperty CreateProperty(string name, JToken value, Renderi
return new LogEventProperty(name, CreatePropertyValue(value, renderings));
}
- static LogEventPropertyValue CreatePropertyValue(JToken value, Rendering[]? renderings)
+ static LogEventPropertyValue CreatePropertyValue(in JsonElement value, Rendering[]? renderings)
{
- if (value.Type == JTokenType.Null)
+ if (value.ValueKind == JsonValueKind.Null)
return new ScalarValue(null);
- if (value is JObject obj)
+ if (value.ValueKind == JsonValueKind.Object)
{
- obj.TryGetValue(TypeTagPropertyName, out var tt);
+ string? tts = null;
+ if (value.TryGetProperty(TypeTagPropertyName, out var tt))
+ tts = tt.GetString();
return new StructureValue(
- obj.Properties().Where(kvp => kvp.Name != TypeTagPropertyName).Select(kvp => CreateProperty(kvp.Name, kvp.Value, null)),
- tt?.Value());
+ value.EnumerateObject().Where(kvp => kvp.Name != TypeTagPropertyName).Select(kvp => CreateProperty(kvp.Name, kvp.Value, null)),
+ tts);
}
- if (value is JArray arr)
+ if (value.ValueKind == JsonValueKind.Array)
{
- return new SequenceValue(arr.Select(v => CreatePropertyValue(v, null)));
+ return new SequenceValue(value.EnumerateArray().Select(v => CreatePropertyValue(v, null)));
}
- var raw = value.Value()!.Value;
+ object? raw = null;
+ switch (value.ValueKind)
+ {
+ case JsonValueKind.String:
+ raw = value.GetString(); break;
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ raw = value.GetBoolean(); break;
+ case JsonValueKind.Number:
+ {
+ if (value.TryGetInt64(out var x))
+ {
+ raw = x; break;
+ }
+ }
+ {
+ if (value.TryGetUInt64(out var x))
+ {
+ raw = x; break;
+ }
+ }
+ {
+ if (value.TryGetDouble(out var x))
+ {
+ raw = x; break;
+ }
+ }
+ {
+ if (value.TryGetDecimal(out var x))
+ {
+ raw = x; break;
+ }
+ }
+ break;
+ }
+ if (raw == null)
+ raw = value.GetRawText();
- return renderings != null && renderings.Length != 0 ?
+ return renderings != null && renderings.Length != 0 ?
new RenderableScalarValue(raw, renderings) :
new ScalarValue(raw);
}
diff --git a/src/Serilog.Formatting.Compact.Reader/Serilog.Formatting.Compact.Reader.csproj b/src/Serilog.Formatting.Compact.Reader/Serilog.Formatting.Compact.Reader.csproj
index fd772a2..421b7de 100644
--- a/src/Serilog.Formatting.Compact.Reader/Serilog.Formatting.Compact.Reader.csproj
+++ b/src/Serilog.Formatting.Compact.Reader/Serilog.Formatting.Compact.Reader.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/test/Serilog.Formatting.Compact.Reader.Tests/LogEventReaderTests.cs b/test/Serilog.Formatting.Compact.Reader.Tests/LogEventReaderTests.cs
index 4c36b5c..d25f4ac 100644
--- a/test/Serilog.Formatting.Compact.Reader.Tests/LogEventReaderTests.cs
+++ b/test/Serilog.Formatting.Compact.Reader.Tests/LogEventReaderTests.cs
@@ -1,5 +1,4 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using System.Text.Json;
using Serilog.Events;
using System;
using System.Collections.Generic;
@@ -72,8 +71,8 @@ public void MessagesAreEscapedIntoTemplates()
public void HandlesDefaultJsonNetSerialization()
{
const string document = "{\"@t\":\"2016-10-12T04:20:58.0554314Z\",\"@m\":\"Hello\"}";
- var jObject = JsonConvert.DeserializeObject(document);
- var evt = LogEventReader.ReadFromJObject(jObject);
+ using var jd = JsonDocument.Parse(document);
+ var evt = LogEventReader.ReadFromJObject(jd.RootElement);
Assert.Equal(DateTimeOffset.Parse("2016-10-12T04:20:58.0554314Z"), evt.Timestamp);
}
diff --git a/test/Serilog.Formatting.Compact.Reader.Tests/PropertyFactoryTests.cs b/test/Serilog.Formatting.Compact.Reader.Tests/PropertyFactoryTests.cs
index 040723a..2fe9ae3 100644
--- a/test/Serilog.Formatting.Compact.Reader.Tests/PropertyFactoryTests.cs
+++ b/test/Serilog.Formatting.Compact.Reader.Tests/PropertyFactoryTests.cs
@@ -1,5 +1,5 @@
-using Newtonsoft.Json.Linq;
-using Serilog.Events;
+using Serilog.Events;
+using System.Text.Json;
using Xunit;
namespace Serilog.Formatting.Compact.Reader.Tests;
@@ -11,7 +11,8 @@ public void PropertiesAreConstructed()
{
const string name = "Test";
const string value = "Value";
- var p = PropertyFactory.CreateProperty(name, new JValue(value), null);
+ using var jd = JsonDocument.Parse($"{{\"{name}\": \"{value}\"}}");
+ var p = PropertyFactory.CreateProperty(name, jd.RootElement.GetProperty(name), null);
Assert.Equal(name, p.Name);
var s = Assert.IsType(p.Value);
Assert.Equal(value, s.Value);
@@ -21,7 +22,8 @@ public void PropertiesAreConstructed()
public void InvalidPropertyNamesAreSubstituted()
{
const string name = "";
- var p = PropertyFactory.CreateProperty(name, new JValue((object)null), null);
+ using var jd = JsonDocument.Parse("null");
+ var p = PropertyFactory.CreateProperty(name, jd.RootElement, null);
Assert.NotEqual(name, p.Name);
}
}
\ No newline at end of file