diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs index 3edacd35c1d4..ebb1d752e648 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs @@ -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 AdditionalProperties { get; } + } public partial interface IJsonModel : System.ClientModel.Primitives.IPersistableModel { T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options); @@ -118,6 +122,21 @@ public partial interface IPersistableModel string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options); System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options); } + public abstract partial class JsonModel : System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IPersistableModel + { + protected JsonModel() { } + System.Collections.Generic.IDictionary 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.Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { } + T System.ClientModel.Primitives.IPersistableModel.Create(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + System.BinaryData System.ClientModel.Primitives.IPersistableModel.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; } diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs index 2367ba7f518d..775f20d55344 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs @@ -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 AdditionalProperties { get; } + } public partial interface IJsonModel : System.ClientModel.Primitives.IPersistableModel { T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options); @@ -118,6 +122,21 @@ public partial interface IPersistableModel string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options); System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options); } + public abstract partial class JsonModel : System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IJsonModel, System.ClientModel.Primitives.IPersistableModel + { + protected JsonModel() { } + System.Collections.Generic.IDictionary 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.Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf8JsonWriter writer, System.ClientModel.Primitives.ModelReaderWriterOptions options) { } + T System.ClientModel.Primitives.IPersistableModel.Create(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } + System.BinaryData System.ClientModel.Primitives.IPersistableModel.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; } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs index 0fc498a4da62..7c3237071a78 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs @@ -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; -/// -/// Allows an object to control its own JSON writing and reading. -/// -/// The type the model can be converted into. -public interface IJsonModel : IPersistableModel +#pragma warning disable CS1591 // public XML comments +public interface IJsonModel { - /// - /// Writes the model to the provided . - /// - /// The to write into. - /// The to use. - /// If the model does not support the requested . -#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 - - /// - /// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model. - /// - /// The to read. - /// The to use. - /// A representation of the JSON value. - /// If the model does not support the requested . -#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 AdditionalProperties { get; } } +#pragma warning restore CS1591 // public XML comments diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModelOfT.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModelOfT.cs new file mode 100644 index 000000000000..0fc498a4da62 --- /dev/null +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModelOfT.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; + +namespace System.ClientModel.Primitives; + +/// +/// Allows an object to control its own JSON writing and reading. +/// +/// The type the model can be converted into. +public interface IJsonModel : IPersistableModel +{ + /// + /// Writes the model to the provided . + /// + /// The to write into. + /// The to use. + /// If the model does not support the requested . +#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 + + /// + /// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model. + /// + /// The to read. + /// The to use. + /// A representation of the JSON value. + /// If the model does not support the requested . +#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 +} diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModelOfT.cs similarity index 100% rename from sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs rename to sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModelOfT.cs diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelOfT.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelOfT.cs new file mode 100644 index 000000000000..2e3e663ece99 --- /dev/null +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelOfT.cs @@ -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 : IJsonModel, IJsonModel +{ + private Dictionary? _unknownProperties; + + IDictionary 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.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + => CreateCore(ref reader, options); + + T IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + var pm = this as IJsonModel; + var reader = new Utf8JsonReader(data); + return pm.Create(ref reader, options); + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) + => GetFormatFromOptionsCore(options); + + protected virtual string GetFormatFromOptionsCore(ModelReaderWriterOptions options) + { + if (options.Format == "W") + return "J"; + + throw new NotSupportedException(); + } + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + => WriteCore(writer, options); + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + var jsonModel = this as IJsonModel; + 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