Skip to content

Add CreateJson overloads to BinaryContent for simplified JSON serialization #50921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ protected AuthenticationTokenProvider() { }
public abstract partial class BinaryContent : System.IDisposable
{
protected BinaryContent() { }
public virtual string? MediaType { get { throw null; } }
public static System.ClientModel.BinaryContent Create(System.BinaryData value) { throw null; }
public static System.ClientModel.BinaryContent Create(System.IO.Stream stream) { throw null; }
public static System.ClientModel.BinaryContent Create<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
public static System.ClientModel.BinaryContent CreateJson<T>(T jsonSerializable, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
public static System.ClientModel.BinaryContent CreateJson<T>(T jsonSerializable, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> jsonTypeInfo) { throw null; }
public abstract void Dispose();
public abstract bool TryComputeLength(out long length);
public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ protected AuthenticationTokenProvider() { }
public abstract partial class BinaryContent : System.IDisposable
{
protected BinaryContent() { }
public virtual string? MediaType { get { throw null; } }
public static System.ClientModel.BinaryContent Create(System.BinaryData value) { throw null; }
public static System.ClientModel.BinaryContent Create(System.IO.Stream stream) { throw null; }
public static System.ClientModel.BinaryContent Create<T>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
public static System.ClientModel.BinaryContent CreateJson<T>(T jsonSerializable, System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
public static System.ClientModel.BinaryContent CreateJson<T>(T jsonSerializable, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> jsonTypeInfo) { throw null; }
public abstract void Dispose();
public abstract bool TryComputeLength(out long length);
public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Expand Down
40 changes: 40 additions & 0 deletions sdk/core/System.ClientModel/src/Message/BinaryContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.ClientModel.Internal;
using System.ClientModel.Primitives;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -18,6 +20,11 @@ public abstract class BinaryContent : IDisposable
{
private static readonly ModelReaderWriterOptions ModelWriteWireOptions = new ModelReaderWriterOptions("W");

/// <summary>
/// Gets the media type of the content.
/// </summary>
public virtual string? MediaType { get; }

/// <summary>
/// Creates an instance of <see cref="BinaryContent"/> that contains the
/// bytes held in the provided <see cref="BinaryData"/> instance.
Expand Down Expand Up @@ -65,6 +72,39 @@ public static BinaryContent Create(Stream stream)
return new StreamBinaryContent(stream);
}

/// <summary>
/// Creates an instance of <see cref="BinaryContent"/> that contains the
/// JSON representation of the provided object.
/// </summary>
/// <typeparam name="T">The type of the object to serialize.</typeparam>
/// <param name="jsonSerializable">The object to serialize to JSON.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use for serialization.
/// If not provided, the default options will be used.</param>
/// <returns>An instance of <see cref="BinaryContent"/> that contains the
/// JSON representation of the provided object.</returns>
#pragma warning disable AZC0014 // Avoid using banned types in public API
public static BinaryContent CreateJson<T>(T jsonSerializable, JsonSerializerOptions? options = default)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
return Create(BinaryData.FromObjectAsJson(jsonSerializable, options));
}

/// <summary>
/// Creates an instance of <see cref="BinaryContent"/> that contains the
/// JSON representation of the provided object using the specified JSON type information.
/// </summary>
/// <typeparam name="T">The type of the object to serialize.</typeparam>
/// <param name="jsonSerializable">The object to serialize to JSON.</param>
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo{T}"/> to use for serialization.</param>
/// <returns>An instance of <see cref="BinaryContent"/> that contains the
/// JSON representation of the provided object.</returns>
#pragma warning disable AZC0014 // Avoid using banned types in public API
public static BinaryContent CreateJson<T>(T jsonSerializable, JsonTypeInfo<T> jsonTypeInfo)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
return Create(BinaryData.FromObjectAsJson(jsonSerializable, jsonTypeInfo));
}

/// <summary>
/// Attempts to compute the length of the underlying body content, if available.
/// </summary>
Expand Down
56 changes: 56 additions & 0 deletions sdk/core/System.ClientModel/tests/Message/BinaryContentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.ClientModel.Primitives;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.TestFramework;
Expand Down Expand Up @@ -156,4 +157,59 @@ public void StreamBinaryContentMustBeSeekable()

Assert.Throws<ArgumentException>(() => { BinaryContent.Create(stream); });
}

[Test]
public async Task CanCreateAndWriteJsonBinaryContentFromObject()
{
var testObject = new { Name = "test", Value = 42 };
using BinaryContent content = BinaryContent.CreateJson(testObject);

Assert.IsTrue(content.TryComputeLength(out long length));
Assert.Greater(length, 0);

MemoryStream stream = new MemoryStream();
await content.WriteToSyncOrAsync(stream, CancellationToken.None, IsAsync);

string json = System.Text.Encoding.UTF8.GetString(stream.ToArray());
Assert.IsTrue(json.Contains("test"));
Assert.IsTrue(json.Contains("42"));
}

[Test]
public async Task CanCreateAndWriteJsonBinaryContentWithOptions()
{
var testObject = new { Name = "TEST", Value = 42 };
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };

using BinaryContent content = BinaryContent.CreateJson(testObject, options);

Assert.IsTrue(content.TryComputeLength(out long length));
Assert.Greater(length, 0);

MemoryStream stream = new MemoryStream();
await content.WriteToSyncOrAsync(stream, CancellationToken.None, IsAsync);

string json = System.Text.Encoding.UTF8.GetString(stream.ToArray());
// With camelCase naming policy, "Name" should become "name"
Assert.IsTrue(json.Contains("name"));
Assert.IsTrue(json.Contains("value"));
}

[Test]
public void JsonBinaryContentMatchesBinaryDataFromObjectAsJson()
{
var testObject = new { Name = "test", Value = 42 };

// Create using the new CreateJson method
using BinaryContent content = BinaryContent.CreateJson(testObject);

// Create using the existing pattern
BinaryData binaryData = BinaryData.FromObjectAsJson(testObject);
using BinaryContent expectedContent = BinaryContent.Create(binaryData);

// They should have the same length
Assert.IsTrue(content.TryComputeLength(out long contentLength));
Assert.IsTrue(expectedContent.TryComputeLength(out long expectedLength));
Assert.AreEqual(expectedLength, contentLength);
}
}
Loading