Skip to content
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

Prototype of JsonModel<T> abstract class #43571

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMe
protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { }
protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; }
}
public partial interface IJsonModel
{
System.Collections.Generic.IDictionary<string, object> AdditionalProperties { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be appropriate that we call it AdditionalProperties?
A generated model in our current generated libraries might have AdditionalProperties property if this is defined in the spec, and its type is usually IDictionary<string, BinaryData> or IReadOnlyDictionary<string, BinaryData>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point, and this is currently an open question under discussion - whether we should merge this feature with the existing AdditionalProperties feature. It is currently unclear whether we would want multiple property dictionaries on a type -- it sounds like we would prefer a single dictionary that supported both/all three cases (with _serializedAdditionalRawData as well), but the details of how this would work have not been finalized.

}
public partial interface IJsonModel<out T> : System.ClientModel.Primitives.IPersistableModel<T>
{
T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options);
Expand All @@ -118,6 +122,21 @@ public partial interface IPersistableModel<out T>
string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options);
System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options);
}
public abstract partial class JsonModel<T> : System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IJsonModel<T>, System.ClientModel.Primitives.IPersistableModel<T>
{
protected JsonModel() { }
System.Collections.Generic.IDictionary<string, object> System.ClientModel.Primitives.IJsonModel.AdditionalProperties { get { throw null; } }
protected abstract T CreateCore(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options);
protected virtual string GetFormatFromOptionsCore(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
protected void ReadUnknownProperty(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
T System.ClientModel.Primitives.IJsonModel<T>.Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
void System.ClientModel.Primitives.IJsonModel<T>.Write(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
T System.ClientModel.Primitives.IPersistableModel<T>.Create(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
string System.ClientModel.Primitives.IPersistableModel<T>.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
System.BinaryData System.ClientModel.Primitives.IPersistableModel<T>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
protected abstract void WriteCore(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options);
protected void WriteUnknownProperties(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
}
public static partial class ModelReaderWriter
{
public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMe
protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { }
protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; }
}
public partial interface IJsonModel
{
System.Collections.Generic.IDictionary<string, object> AdditionalProperties { get; }
}
public partial interface IJsonModel<out T> : System.ClientModel.Primitives.IPersistableModel<T>
{
T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options);
Expand All @@ -118,6 +122,21 @@ public partial interface IPersistableModel<out T>
string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options);
System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options);
}
public abstract partial class JsonModel<T> : System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IJsonModel<T>, System.ClientModel.Primitives.IPersistableModel<T>
{
protected JsonModel() { }
System.Collections.Generic.IDictionary<string, object> System.ClientModel.Primitives.IJsonModel.AdditionalProperties { get { throw null; } }
protected abstract T CreateCore(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options);
protected virtual string GetFormatFromOptionsCore(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
protected void ReadUnknownProperty(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
T System.ClientModel.Primitives.IJsonModel<T>.Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
void System.ClientModel.Primitives.IJsonModel<T>.Write(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
T System.ClientModel.Primitives.IPersistableModel<T>.Create(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
string System.ClientModel.Primitives.IPersistableModel<T>.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
System.BinaryData System.ClientModel.Primitives.IPersistableModel<T>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
protected abstract void WriteCore(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options);
protected void WriteUnknownProperties(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { }
}
public static partial class ModelReaderWriter
{
public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; }
Expand Down
31 changes: 5 additions & 26 deletions sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Text.Json;
using System.Collections.Generic;

namespace System.ClientModel.Primitives;

/// <summary>
/// Allows an object to control its own JSON writing and reading.
/// </summary>
/// <typeparam name="T">The type the model can be converted into.</typeparam>
public interface IJsonModel<out T> : IPersistableModel<T>
#pragma warning disable CS1591 // public XML comments
public interface IJsonModel
{
/// <summary>
/// Writes the model to the provided <see cref="Utf8JsonWriter"/>.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write into.</param>
/// <param name="options">The <see cref="ModelReaderWriterOptions"/> to use.</param>
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
#pragma warning disable AZC0014 // Avoid using banned types in public API
void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options);
#pragma warning restore AZC0014 // Avoid using banned types in public API

/// <summary>
/// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model.
/// </summary>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read.</param>
/// <param name="options">The <see cref="ModelReaderWriterOptions"/> to use.</param>
/// <returns>A <typeparamref name="T"/> representation of the JSON value.</returns>
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
#pragma warning disable AZC0014 // Avoid using banned types in public API
T Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options);
#pragma warning restore AZC0014 // Avoid using banned types in public API
IDictionary<string, object> AdditionalProperties { get; }
}
#pragma warning restore CS1591 // public XML comments
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Text.Json;

namespace System.ClientModel.Primitives;

/// <summary>
/// Allows an object to control its own JSON writing and reading.
/// </summary>
/// <typeparam name="T">The type the model can be converted into.</typeparam>
public interface IJsonModel<out T> : IPersistableModel<T>
{
/// <summary>
/// Writes the model to the provided <see cref="Utf8JsonWriter"/>.
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to write into.</param>
/// <param name="options">The <see cref="ModelReaderWriterOptions"/> to use.</param>
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
#pragma warning disable AZC0014 // Avoid using banned types in public API
void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options);
#pragma warning restore AZC0014 // Avoid using banned types in public API

/// <summary>
/// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model.
/// </summary>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read.</param>
/// <param name="options">The <see cref="ModelReaderWriterOptions"/> to use.</param>
/// <returns>A <typeparamref name="T"/> representation of the JSON value.</returns>
/// <exception cref="FormatException">If the model does not support the requested <see cref="ModelReaderWriterOptions.Format"/>.</exception>
#pragma warning disable AZC0014 // Avoid using banned types in public API
T Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options);
#pragma warning restore AZC0014 // Avoid using banned types in public API
}
175 changes: 175 additions & 0 deletions sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelOfT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.IO;
using System.Text.Json;

namespace System.ClientModel.Primitives;

#pragma warning disable CS1591 // public XML comments
public abstract class JsonModel<T> : IJsonModel, IJsonModel<T>
{
private Dictionary<string, object>? _unknownProperties;

IDictionary<string, object> IJsonModel.AdditionalProperties
=> _unknownProperties ??= new();

#pragma warning disable AZC0014 // Avoid using banned types in public API
protected abstract T CreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options);

protected abstract void WriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options);
#pragma warning restore AZC0014 // Avoid using banned types in public API

T IJsonModel<T>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
=> CreateCore(ref reader, options);

T IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options)
{
var pm = this as IJsonModel<T>;
var reader = new Utf8JsonReader(data);
return pm.Create(ref reader, options);
}

string IPersistableModel<T>.GetFormatFromOptions(ModelReaderWriterOptions options)
=> GetFormatFromOptionsCore(options);

protected virtual string GetFormatFromOptionsCore(ModelReaderWriterOptions options)
{
if (options.Format == "W")
return "J";

throw new NotSupportedException();
}

void IJsonModel<T>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
=> WriteCore(writer, options);

BinaryData IPersistableModel<T>.Write(ModelReaderWriterOptions options)
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);

var jsonModel = this as IJsonModel<T>;
jsonModel.Write(writer, options);
stream.Position = 0;
// TODO: flush?

return BinaryData.FromStream(stream);
}

private static BinaryData ReadUnknownValue(ref Utf8JsonReader reader)
{
var stream = new MemoryStream();
int depth = 0;
#if NET6_0_OR_GREATER
bool writeComma = false;
#endif

// TODO: netstandard2.0

while (true)
{
if (!reader.Read())
throw new Exception();

switch (reader.TokenType)
{
case JsonTokenType.String:
if (depth == 0)
{
return new BinaryData(reader.ValueSpan.ToArray());
}
#if NET6_0_OR_GREATER
stream.Write("\""u8);
stream.Write(reader.ValueSpan);
stream.Write("\""u8);
writeComma = true;
#endif
break;
case JsonTokenType.Number:
if (depth == 0)
{
return new BinaryData(reader.ValueSpan.ToArray());
}
#if NET6_0_OR_GREATER
stream.Write(reader.ValueSpan);
writeComma = true;
#endif
break;
case JsonTokenType.EndObject:
case JsonTokenType.EndArray:
#if NET6_0_OR_GREATER
stream.Write(reader.ValueSpan);
#endif
depth--;
if (depth == 0)
{
stream.Position = 0;
return BinaryData.FromStream(stream);
}
break;
case JsonTokenType.StartObject:
case JsonTokenType.StartArray:
depth++;
#if NET6_0_OR_GREATER
stream.Write(reader.ValueSpan);
#endif
break;
case JsonTokenType.PropertyName:
#if NET6_0_OR_GREATER
if (writeComma)
{
stream.Write(",\n"u8);
writeComma = false;
}
stream.Write("\""u8);
stream.Write(reader.ValueSpan);
stream.Write("\":"u8);
#endif
break;

default:
throw new NotImplementedException();
}
}
}

#pragma warning disable AZC0014 // Avoid using banned types in public API
protected void ReadUnknownProperty(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
string name = reader.GetString()!;
BinaryData value = ReadUnknownValue(ref reader);
((IJsonModel)this).AdditionalProperties.Add(name, value);
}

#pragma warning disable AZC0014 // Avoid using banned types in public API
protected void WriteUnknownProperties(Utf8JsonWriter writer, ModelReaderWriterOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
if (_unknownProperties != null)
{
foreach (var property in _unknownProperties)
{
// Skip non-serialized items for now
// TODO: serialize non-serialized items in some cases?
if (property.Value is not BinaryData serializedValue)
{
continue;
}

writer.WritePropertyName(property.Key);
#if NET6_0_OR_GREATER
writer.WriteRawValue(serializedValue);
#else
using (JsonDocument document = JsonDocument.Parse(serializedValue))
{
JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
}
}
}
#pragma warning restore CS1591 // public XML comments
Loading