Skip to content

Commit

Permalink
Implement public JsonElement conversions in JsonEventFormatter
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
jskeet committed Aug 18, 2022
1 parent afaf830 commit d9e10ea
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
31 changes: 31 additions & 0 deletions src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml.Linq;

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

/// <summary>
/// Converts the given <see cref="JsonElement"/> into a <see cref="CloudEvent"/>.
/// </summary>
/// <param name="element">The JSON representation of a CloudEvent. Must have a <see cref="JsonElement.ValueKind"/> of <see cref="JsonValueKind.Object"/>.</param>
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
/// <returns>The SDK representation of the CloudEvent.</returns>
public CloudEvent ConvertFromJsonElement(JsonElement element, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
DecodeJsonElement(element, extensionAttributes, nameof(element));

private async Task<IReadOnlyList<CloudEvent>> DecodeBatchModeMessageImpl(Stream data, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes, bool async)
{
Validation.CheckNotNull(data, nameof(data));
Expand Down Expand Up @@ -428,6 +438,27 @@ public override ReadOnlyMemory<byte> EncodeStructuredModeMessage(CloudEvent clou
return stream.ToArray();
}

/// <summary>
/// Converts the given <see cref="CloudEvent"/> to a <see cref="JsonElement"/> containing the structured mode JSON format representation
/// of the event.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="cloudEvent">The event to convert. Must not be null.</param>
/// <returns>A <see cref="JsonElement"/> containing the structured mode JSON format representation of the event.</returns>
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit d9e10ea

Please sign in to comment.