Skip to content

Commit d9e10ea

Browse files
committed
Implement public JsonElement conversions in JsonEventFormatter
The method names are consistent with the ones in ProtobufEventFormatter and the Json.NET event formatter. Currently batch conversion is not supported, but we can add that later if requested. The ConvertToJsonElement method is horribly inefficient at the moment (serialize then parse); we can consider how to make it more efficient later if necessary. Signed-off-by: Jon Skeet <[email protected]>
1 parent afaf830 commit d9e10ea

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Text;
1111
using System.Text.Json;
1212
using System.Threading.Tasks;
13+
using System.Xml.Linq;
1314

1415
namespace CloudNative.CloudEvents.SystemTextJson
1516
{
@@ -157,6 +158,15 @@ public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(Stream body, Co
157158
public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory<byte> body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
158159
DecodeBatchModeMessageImpl(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes, false).GetAwaiter().GetResult();
159160

161+
/// <summary>
162+
/// Converts the given <see cref="JsonElement"/> into a <see cref="CloudEvent"/>.
163+
/// </summary>
164+
/// <param name="element">The JSON representation of a CloudEvent. Must have a <see cref="JsonElement.ValueKind"/> of <see cref="JsonValueKind.Object"/>.</param>
165+
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
166+
/// <returns>The SDK representation of the CloudEvent.</returns>
167+
public CloudEvent ConvertFromJsonElement(JsonElement element, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
168+
DecodeJsonElement(element, extensionAttributes, nameof(element));
169+
160170
private async Task<IReadOnlyList<CloudEvent>> DecodeBatchModeMessageImpl(Stream data, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes, bool async)
161171
{
162172
Validation.CheckNotNull(data, nameof(data));
@@ -428,6 +438,27 @@ public override ReadOnlyMemory<byte> EncodeStructuredModeMessage(CloudEvent clou
428438
return stream.ToArray();
429439
}
430440

441+
/// <summary>
442+
/// Converts the given <see cref="CloudEvent"/> to a <see cref="JsonElement"/> containing the structured mode JSON format representation
443+
/// of the event.
444+
/// </summary>
445+
/// <remarks>The current implementation of this method is inefficient. Care should be taken before
446+
/// using this in performance-sensitive scenarios. The efficiency may well be improved in the future.</remarks>
447+
/// <param name="cloudEvent">The event to convert. Must not be null.</param>
448+
/// <returns>A <see cref="JsonElement"/> containing the structured mode JSON format representation of the event.</returns>
449+
public JsonElement ConvertToJsonElement(CloudEvent cloudEvent)
450+
{
451+
// Unfortunately System.Text.Json doesn't have an equivalent of JTokenWriter,
452+
// so we serialize all the data then parse it. That's horrible, but at least
453+
// it's contained in this one place (rather than in user code everywhere else).
454+
// We can optimize it later by duplicating the logic of WriteCloudEventForBatchOrStructuredMode
455+
// to use System.Text.Json.Nodes.
456+
var data = EncodeStructuredModeMessage(cloudEvent, out _);
457+
using var document = JsonDocument.Parse(data);
458+
// We have to clone the data so that we can dispose of the JsonDocument.
459+
return document.RootElement.Clone();
460+
}
461+
431462
private Utf8JsonWriter CreateUtf8JsonWriter(Stream stream)
432463
{
433464
var options = new JsonWriterOptions

test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache 2.0 license.
33
// See LICENSE file in the project root for full license information.
44

5+
using CloudNative.CloudEvents.Core;
56
using CloudNative.CloudEvents.UnitTests;
67
using System;
78
using System.Collections.Generic;
@@ -15,10 +16,9 @@
1516
using System.Threading.Tasks;
1617
using Xunit;
1718
using static CloudNative.CloudEvents.UnitTests.TestHelpers;
19+
using JArray = Newtonsoft.Json.Linq.JArray;
1820
// JObject is a really handy way of creating JSON which we can then parse with System.Text.Json
1921
using JObject = Newtonsoft.Json.Linq.JObject;
20-
using JArray = Newtonsoft.Json.Linq.JArray;
21-
using CloudNative.CloudEvents.Core;
2222

2323
namespace CloudNative.CloudEvents.SystemTextJson.UnitTests
2424
{
@@ -1102,6 +1102,53 @@ public void EncodeStructured_IndentationSettings()
11021102
Assert.Equal(expected, json);
11031103
}
11041104

1105+
[Fact]
1106+
public void ConvertToJsonElement()
1107+
{
1108+
var cloudEvent = new CloudEvent
1109+
{
1110+
Data = SampleBinaryData
1111+
}.PopulateRequiredAttributes();
1112+
1113+
JsonElement element = new JsonEventFormatter().ConvertToJsonElement(cloudEvent);
1114+
var asserter = new JsonElementAsserter
1115+
{
1116+
{ "data_base64", JsonValueKind.String, SampleBinaryDataBase64 },
1117+
{ "id", JsonValueKind.String, "test-id" },
1118+
{ "source", JsonValueKind.String, "//test" },
1119+
{ "specversion", JsonValueKind.String, "1.0" },
1120+
{ "type", JsonValueKind.String, "test-type" }
1121+
};
1122+
asserter.AssertProperties(element, assertCount: true);
1123+
}
1124+
1125+
[Fact]
1126+
public void ConvertFromJsonElement()
1127+
{
1128+
var obj = new JObject
1129+
{
1130+
["specversion"] = "1.0",
1131+
["type"] = "test-type",
1132+
["id"] = "test-id",
1133+
["data"] = "text", // Just so that it's reasonable to have a DataContentType,
1134+
["datacontenttype"] = "text/plain",
1135+
["dataschema"] = "https://data-schema",
1136+
["subject"] = "event-subject",
1137+
["source"] = "//event-source",
1138+
["time"] = SampleTimestampText
1139+
};
1140+
using var document = JsonDocument.Parse(obj.ToString());
1141+
var cloudEvent = new JsonEventFormatter().ConvertFromJsonElement(document.RootElement, extensionAttributes: null);
1142+
Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion);
1143+
Assert.Equal("test-type", cloudEvent.Type);
1144+
Assert.Equal("test-id", cloudEvent.Id);
1145+
Assert.Equal("text/plain", cloudEvent.DataContentType);
1146+
Assert.Equal(new Uri("https://data-schema"), cloudEvent.DataSchema);
1147+
Assert.Equal("event-subject", cloudEvent.Subject);
1148+
Assert.Equal(new Uri("//event-source", UriKind.RelativeOrAbsolute), cloudEvent.Source);
1149+
AssertTimestampsEqual(SampleTimestamp, cloudEvent.Time);
1150+
}
1151+
11051152
// Utility methods
11061153
private static object DecodeBinaryModeEventData(byte[] bytes, string contentType)
11071154
{

0 commit comments

Comments
 (0)