diff --git a/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs b/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs index 1b612a0..decdb77 100644 --- a/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs +++ b/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs @@ -10,6 +10,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; +using System.Xml.Linq; namespace CloudNative.CloudEvents.SystemTextJson { @@ -157,6 +158,15 @@ public override IReadOnlyList DecodeBatchModeMessage(Stream body, Co public override IReadOnlyList DecodeBatchModeMessage(ReadOnlyMemory body, ContentType? contentType, IEnumerable? extensionAttributes) => DecodeBatchModeMessageImpl(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes, false).GetAwaiter().GetResult(); + /// + /// Converts the given into a . + /// + /// The JSON representation of a CloudEvent. Must have a of . + /// The extension attributes to use when populating the CloudEvent. May be null. + /// The SDK representation of the CloudEvent. + public CloudEvent ConvertFromJsonElement(JsonElement element, IEnumerable? extensionAttributes) => + DecodeJsonElement(element, extensionAttributes, nameof(element)); + private async Task> DecodeBatchModeMessageImpl(Stream data, ContentType? contentType, IEnumerable? extensionAttributes, bool async) { Validation.CheckNotNull(data, nameof(data)); @@ -428,6 +438,27 @@ public override ReadOnlyMemory EncodeStructuredModeMessage(CloudEvent clou return stream.ToArray(); } + /// + /// Converts the given to a containing the structured mode JSON format representation + /// of the event. + /// + /// The current implementation of this method is inefficient. Care should be taken before + /// using this in performance-sensitive scenarios. The efficiency may well be improved in the future. + /// The event to convert. Must not be null. + /// A containing the structured mode JSON format representation of the event. + public JsonElement ConvertToJsonElement(CloudEvent cloudEvent) + { + // Unfortunately System.Text.Json doesn't have an equivalent of JTokenWriter, + // so we serialize all the data then parse it. That's horrible, but at least + // it's contained in this one place (rather than in user code everywhere else). + // We can optimize it later by duplicating the logic of WriteCloudEventForBatchOrStructuredMode + // to use System.Text.Json.Nodes. + var data = EncodeStructuredModeMessage(cloudEvent, out _); + using var document = JsonDocument.Parse(data); + // We have to clone the data so that we can dispose of the JsonDocument. + return document.RootElement.Clone(); + } + private Utf8JsonWriter CreateUtf8JsonWriter(Stream stream) { var options = new JsonWriterOptions diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs index 3e456c9..dd48c50 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 license. // See LICENSE file in the project root for full license information. +using CloudNative.CloudEvents.Core; using CloudNative.CloudEvents.UnitTests; using System; using System.Collections.Generic; @@ -15,10 +16,9 @@ using System.Threading.Tasks; using Xunit; using static CloudNative.CloudEvents.UnitTests.TestHelpers; +using JArray = Newtonsoft.Json.Linq.JArray; // JObject is a really handy way of creating JSON which we can then parse with System.Text.Json using JObject = Newtonsoft.Json.Linq.JObject; -using JArray = Newtonsoft.Json.Linq.JArray; -using CloudNative.CloudEvents.Core; namespace CloudNative.CloudEvents.SystemTextJson.UnitTests { @@ -1102,6 +1102,53 @@ public void EncodeStructured_IndentationSettings() Assert.Equal(expected, json); } + [Fact] + public void ConvertToJsonElement() + { + var cloudEvent = new CloudEvent + { + Data = SampleBinaryData + }.PopulateRequiredAttributes(); + + JsonElement element = new JsonEventFormatter().ConvertToJsonElement(cloudEvent); + var asserter = new JsonElementAsserter + { + { "data_base64", JsonValueKind.String, SampleBinaryDataBase64 }, + { "id", JsonValueKind.String, "test-id" }, + { "source", JsonValueKind.String, "//test" }, + { "specversion", JsonValueKind.String, "1.0" }, + { "type", JsonValueKind.String, "test-type" } + }; + asserter.AssertProperties(element, assertCount: true); + } + + [Fact] + public void ConvertFromJsonElement() + { + var obj = new JObject + { + ["specversion"] = "1.0", + ["type"] = "test-type", + ["id"] = "test-id", + ["data"] = "text", // Just so that it's reasonable to have a DataContentType, + ["datacontenttype"] = "text/plain", + ["dataschema"] = "https://data-schema", + ["subject"] = "event-subject", + ["source"] = "//event-source", + ["time"] = SampleTimestampText + }; + using var document = JsonDocument.Parse(obj.ToString()); + var cloudEvent = new JsonEventFormatter().ConvertFromJsonElement(document.RootElement, extensionAttributes: null); + Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal("text/plain", cloudEvent.DataContentType); + Assert.Equal(new Uri("https://data-schema"), cloudEvent.DataSchema); + Assert.Equal("event-subject", cloudEvent.Subject); + Assert.Equal(new Uri("//event-source", UriKind.RelativeOrAbsolute), cloudEvent.Source); + AssertTimestampsEqual(SampleTimestamp, cloudEvent.Time); + } + // Utility methods private static object DecodeBinaryModeEventData(byte[] bytes, string contentType) {