Skip to content

Commit afaf830

Browse files
committed
Implement public JObject conversions in JsonEventFormatter
The method names are consistent with the ones in ProtobufEventFormatter. Currently batch conversion is not supported, but we can add that later if requested. Signed-off-by: Jon Skeet <[email protected]>
1 parent aa7429d commit afaf830

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public override async Task<CloudEvent> DecodeStructuredModeMessageAsync(Stream b
136136

137137
var jsonReader = CreateJsonReader(body, MimeUtilities.GetEncoding(contentType));
138138
var jObject = await JObject.LoadAsync(jsonReader).ConfigureAwait(false);
139-
return DecodeJObject(jObject, extensionAttributes);
139+
return DecodeJObject(jObject, extensionAttributes, nameof(body));
140140
}
141141

142142
/// <inheritdoc />
@@ -146,7 +146,7 @@ public override CloudEvent DecodeStructuredModeMessage(Stream body, ContentType?
146146

147147
var jsonReader = CreateJsonReader(body, MimeUtilities.GetEncoding(contentType));
148148
var jObject = JObject.Load(jsonReader);
149-
return DecodeJObject(jObject, extensionAttributes);
149+
return DecodeJObject(jObject, extensionAttributes, nameof(body));
150150
}
151151

152152
/// <inheritdoc />
@@ -177,14 +177,23 @@ public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(Stream body, Co
177177
public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory<byte> body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
178178
DecodeBatchModeMessage(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes);
179179

180+
/// <summary>
181+
/// Converts the given <see cref="JObject"/> into a <see cref="CloudEvent"/>.
182+
/// </summary>
183+
/// <param name="jObject">The JSON representation of a CloudEvent. Must not be null.</param>
184+
/// <param name="extensionAttributes">The extension attributes to use when populating the CloudEvent. May be null.</param>
185+
/// <returns>The SDK representation of the CloudEvent.</returns>
186+
public CloudEvent ConvertFromJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
187+
DecodeJObject(Validation.CheckNotNull(jObject, nameof(jObject)), extensionAttributes, nameof(jObject));
188+
180189
private IReadOnlyList<CloudEvent> DecodeJArray(JArray jArray, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
181190
{
182191
List<CloudEvent> events = new List<CloudEvent>(jArray.Count);
183192
foreach (var token in jArray)
184193
{
185194
if (token is JObject obj)
186195
{
187-
events.Add(DecodeJObject(obj, extensionAttributes));
196+
events.Add(DecodeJObject(obj, extensionAttributes, paramName));
188197
}
189198
else
190199
{
@@ -194,7 +203,7 @@ private IReadOnlyList<CloudEvent> DecodeJArray(JArray jArray, IEnumerable<CloudE
194203
return events;
195204
}
196205

197-
private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes)
206+
private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
198207
{
199208
if (!jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken)
200209
|| specVersionToken.Type != JTokenType.String)
@@ -207,9 +216,7 @@ private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribut
207216
var cloudEvent = new CloudEvent(specVersion, extensionAttributes);
208217
PopulateAttributesFromStructuredEvent(cloudEvent, jObject);
209218
PopulateDataFromStructuredEvent(cloudEvent, jObject);
210-
// "data" is always the parameter from the public method. It's annoying not to be able to use
211-
// nameof here, but this will give the appropriate result.
212-
return Validation.CheckCloudEventArgument(cloudEvent, "data");
219+
return Validation.CheckCloudEventArgument(cloudEvent, paramName);
213220
}
214221

215222
private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject)
@@ -395,6 +402,19 @@ public override ReadOnlyMemory<byte> EncodeStructuredModeMessage(CloudEvent clou
395402
return stream.ToArray();
396403
}
397404

405+
/// <summary>
406+
/// Converts the given <see cref="CloudEvent"/> to a <see cref="JObject"/> containing the structured mode JSON format representation
407+
/// of the event.
408+
/// </summary>
409+
/// <param name="cloudEvent">The event to convert. Must not be null.</param>
410+
/// <returns>A <see cref="JObject"/> containing the structured mode JSON format representation of the event.</returns>
411+
public JObject ConvertToJObject(CloudEvent cloudEvent)
412+
{
413+
var writer = new JTokenWriter();
414+
WriteCloudEventForBatchOrStructuredMode(writer, cloudEvent);
415+
return (JObject) writer.Token!;
416+
}
417+
398418
/// <inheritdoc />
399419
public override ReadOnlyMemory<byte> EncodeBatchModeMessage(IEnumerable<CloudEvent> cloudEvents, out ContentType contentType)
400420
{

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,55 @@ public void EncodeStructured_IndentationSettings()
10921092
Assert.Equal(expected, json);
10931093
}
10941094

1095+
// Effectively smoke tests for LINQ to JSON conversions; these piggy-back on the same implementation
1096+
// as the rest of the code, so we don't need to test exhaustively.
1097+
1098+
[Fact]
1099+
public void ConvertToJObject()
1100+
{
1101+
var cloudEvent = new CloudEvent
1102+
{
1103+
Data = SampleBinaryData
1104+
}.PopulateRequiredAttributes();
1105+
1106+
JObject obj = new JsonEventFormatter().ConvertToJObject(cloudEvent);
1107+
var asserter = new JTokenAsserter
1108+
{
1109+
{ "data_base64", JTokenType.String, SampleBinaryDataBase64 },
1110+
{ "id", JTokenType.String, "test-id" },
1111+
{ "source", JTokenType.String, "//test" },
1112+
{ "specversion", JTokenType.String, "1.0" },
1113+
{ "type", JTokenType.String, "test-type" },
1114+
};
1115+
asserter.AssertProperties(obj, assertCount: true);
1116+
}
1117+
1118+
[Fact]
1119+
public void ConvertFromJObject()
1120+
{
1121+
var obj = new JObject
1122+
{
1123+
["specversion"] = "1.0",
1124+
["type"] = "test-type",
1125+
["id"] = "test-id",
1126+
["data"] = "text", // Just so that it's reasonable to have a DataContentType,
1127+
["datacontenttype"] = "text/plain",
1128+
["dataschema"] = "https://data-schema",
1129+
["subject"] = "event-subject",
1130+
["source"] = "//event-source",
1131+
["time"] = SampleTimestampText
1132+
};
1133+
var cloudEvent = new JsonEventFormatter().ConvertFromJObject(obj, extensionAttributes: null);
1134+
Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion);
1135+
Assert.Equal("test-type", cloudEvent.Type);
1136+
Assert.Equal("test-id", cloudEvent.Id);
1137+
Assert.Equal("text/plain", cloudEvent.DataContentType);
1138+
Assert.Equal(new Uri("https://data-schema"), cloudEvent.DataSchema);
1139+
Assert.Equal("event-subject", cloudEvent.Subject);
1140+
Assert.Equal(new Uri("//event-source", UriKind.RelativeOrAbsolute), cloudEvent.Source);
1141+
AssertTimestampsEqual(SampleTimestamp, cloudEvent.Time);
1142+
}
1143+
10951144
// Utility methods
10961145
private static object? DecodeBinaryModeEventData(byte[] bytes, string contentType)
10971146
{

0 commit comments

Comments
 (0)