Skip to content
Merged
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
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net461.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,15 @@ public override void Serialize(System.IO.Stream stream, object? value, System.Ty
public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<System.BinaryData> SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelJsonConverter : System.Text.Json.Serialization.JsonConverter<Azure.Core.Serialization.IJsonSerializable>
{
public ModelJsonConverter() { }
public ModelJsonConverter(bool ignoreAdditionalProperties) { }
public bool IgnoreAdditionalProperties { get { throw null; } }
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override Azure.Core.Serialization.IJsonSerializable Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Serialization.IJsonSerializable value, System.Text.Json.JsonSerializerOptions options) { }
}
public static partial class ModelSerializer
{
public static T Deserialize<T>(System.IO.Stream stream, Azure.Core.Serialization.SerializableOptions? options = null) where T : Azure.Core.Serialization.IJsonSerializable, new() { throw null; }
Expand Down
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net5.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,15 @@ public override void Serialize(System.IO.Stream stream, object? value, System.Ty
public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<System.BinaryData> SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelJsonConverter : System.Text.Json.Serialization.JsonConverter<Azure.Core.Serialization.IJsonSerializable>
{
public ModelJsonConverter() { }
public ModelJsonConverter(bool ignoreAdditionalProperties) { }
public bool IgnoreAdditionalProperties { get { throw null; } }
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override Azure.Core.Serialization.IJsonSerializable Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Serialization.IJsonSerializable value, System.Text.Json.JsonSerializerOptions options) { }
}
public static partial class ModelSerializer
{
public static T Deserialize<T>(System.IO.Stream stream, Azure.Core.Serialization.SerializableOptions? options = null) where T : Azure.Core.Serialization.IJsonSerializable, new() { throw null; }
Expand Down
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.net6.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,15 @@ public override void Serialize(System.IO.Stream stream, object? value, System.Ty
public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<System.BinaryData> SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelJsonConverter : System.Text.Json.Serialization.JsonConverter<Azure.Core.Serialization.IJsonSerializable>
{
public ModelJsonConverter() { }
public ModelJsonConverter(bool ignoreAdditionalProperties) { }
public bool IgnoreAdditionalProperties { get { throw null; } }
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override Azure.Core.Serialization.IJsonSerializable Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Serialization.IJsonSerializable value, System.Text.Json.JsonSerializerOptions options) { }
}
public static partial class ModelSerializer
{
public static T Deserialize<T>(System.IO.Stream stream, Azure.Core.Serialization.SerializableOptions? options = null) where T : Azure.Core.Serialization.IJsonSerializable, new() { throw null; }
Expand Down
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,15 @@ public override void Serialize(System.IO.Stream stream, object? value, System.Ty
public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<System.BinaryData> SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelJsonConverter : System.Text.Json.Serialization.JsonConverter<Azure.Core.Serialization.IJsonSerializable>
{
public ModelJsonConverter() { }
public ModelJsonConverter(bool ignoreAdditionalProperties) { }
public bool IgnoreAdditionalProperties { get { throw null; } }
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override Azure.Core.Serialization.IJsonSerializable Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Serialization.IJsonSerializable value, System.Text.Json.JsonSerializerOptions options) { }
}
public static partial class ModelSerializer
{
public static T Deserialize<T>(System.IO.Stream stream, Azure.Core.Serialization.SerializableOptions? options = null) where T : Azure.Core.Serialization.IJsonSerializable, new() { throw null; }
Expand Down
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,15 @@ public override void Serialize(System.IO.Stream stream, object? value, System.Ty
public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<System.BinaryData> SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelJsonConverter : System.Text.Json.Serialization.JsonConverter<Azure.Core.Serialization.IJsonSerializable>
{
public ModelJsonConverter() { }
public ModelJsonConverter(bool ignoreAdditionalProperties) { }
public bool IgnoreAdditionalProperties { get { throw null; } }
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override Azure.Core.Serialization.IJsonSerializable Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
public override void Write(System.Text.Json.Utf8JsonWriter writer, Azure.Core.Serialization.IJsonSerializable value, System.Text.Json.JsonSerializerOptions options) { }
}
public static partial class ModelSerializer
{
public static T Deserialize<T>(System.IO.Stream stream, Azure.Core.Serialization.SerializableOptions? options = null) where T : Azure.Core.Serialization.IJsonSerializable, new() { throw null; }
Expand Down
32 changes: 32 additions & 0 deletions sdk/core/Azure.Core/samples/Serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,35 @@ string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"","

DogListProperty dog = ModelSerializer.Deserialize<DogListProperty>(json, options);
```

## Using ModelJsonConverter for JsonSerializer
By using the ModelJsonConverter class we can have a place to add additional properties to the JsonSerializerOptions.
This will allow us to add things like `IgnoreAdditionalProperties` and `Version` to the options without needing to have our own ModelSerializer.
The `SerializableOptions` would become internal and we would have a converter to convert from `JsonSerializerOptions` + `ModelJsonConverter` to `SerializableOptions`.

Serialization
```C# Snippet:ModelConverter_Serialize
DogListProperty dog = new DogListProperty
{
Name = "Doggo",
IsHungry = true,
Weight = 1.1,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new ModelJsonConverter(false));

string json = JsonSerializer.Serialize(dog, options);
```

Deserialization

```C# Snippet:ModelConverter_Deserialize
string json = @"[{""LatinName"":""Animalia"",""Weight"":1.1,""Name"":""Doggo"",""IsHungry"":false,""FoodConsumed"":[""kibble"",""egg"",""peanut butter""],""NumberOfLegs"":4}]";

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new ModelJsonConverter(false));

DogListProperty dog = JsonSerializer.Deserialize<DogListProperty>(json, options);
```
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/src/Azure.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<Compile Include="Shared\FixedDelayWithNoJitterStrategy.cs" />
<Compile Include="Shared\HashCodeBuilder.cs" />
<Compile Include="Shared\HttpMessageSanitizer.cs" />
<Compile Include="Shared\IModelInternalSerializable.cs" />
<Compile Include="Shared\InitializationConstructorAttribute.cs" />
<Compile Include="Shared\Multipart\MemoryResponse.cs" />
<Compile Include="Shared\NullableAttributes.cs" />
Expand Down
91 changes: 91 additions & 0 deletions sdk/core/Azure.Core/src/Serialization/ModelJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Azure.Core.Serialization
{
/// <summary>
/// .
/// </summary>
#pragma warning disable AZC0014 // Avoid using banned types in public API
public class ModelJsonConverter : JsonConverter<IJsonSerializable>
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
/// <summary>
/// .
/// </summary>
public bool IgnoreAdditionalProperties { get; }

/// <summary>
/// .
/// </summary>
public ModelJsonConverter()
: this(true) { }

/// <summary>
/// .
/// </summary>
/// <param name="ignoreAdditionalProperties"></param>
public ModelJsonConverter(bool ignoreAdditionalProperties)
{
IgnoreAdditionalProperties = ignoreAdditionalProperties;
}

/// <summary>
/// .
/// </summary>
/// <param name="typeToConvert"></param>
/// <returns></returns>
public override bool CanConvert(Type typeToConvert)
{
return (typeToConvert.GetInterfaces().Any(i => i is IJsonSerializable));
}

/// <summary>
/// .
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
#pragma warning disable AZC0014 // Avoid using banned types in public API
public override IJsonSerializable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
SerializableOptions serializableOptions = ConvertOptions(options);
var model = Activator.CreateInstance(typeToConvert, true) as IModelInternalSerializable;
if (model is null)
throw new NotSupportedException($"{typeToConvert.Name} does not have a parameterless constructor");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

won't Activator.CreateInstance already throw if a suitable constructor is not available? Seems like the error should be related to the not implementing IModelInternalSerializable


model.Deserialize(ref reader, serializableOptions);
return (IJsonSerializable)model;
}

/// <summary>
/// .
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
#pragma warning disable AZC0014 // Avoid using banned types in public API
public override void Write(Utf8JsonWriter writer, IJsonSerializable value, JsonSerializerOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
SerializableOptions serializableOptions = ConvertOptions(options);
((IModelInternalSerializable)value).Serialize(writer, serializableOptions);
}

private SerializableOptions ConvertOptions(JsonSerializerOptions options)
{
SerializableOptions serializableOptions = new SerializableOptions();
serializableOptions.IgnoreAdditionalProperties = IgnoreAdditionalProperties;
serializableOptions.IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties;
return serializableOptions;
}
}
}
25 changes: 25 additions & 0 deletions sdk/core/Azure.Core/src/Shared/IModelInternalSerializable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Text.Json;
using Azure.Core.Serialization;

namespace Azure.Core
{
internal interface IModelInternalSerializable
{
/// <summary>
/// TODO
/// </summary>
/// <param name="writer"></param>
/// <param name="options"></param>
void Serialize(Utf8JsonWriter writer, SerializableOptions? options = default);

/// <summary>
/// TODO
/// </summary>
/// <param name="reader"></param>
/// <param name="options"></param>
void Deserialize(ref Utf8JsonReader reader, SerializableOptions? options = default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Core.Serialization;
using NUnit.Framework;

namespace Azure.Core.Tests.ModelSerializationTests
{
public class ModelJsonConverterTests
{
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void SerializeTest(bool ignoreReadonlyProperties, bool ignoreAdditionalProperties)
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new ModelJsonConverter(ignoreAdditionalProperties));
options.IgnoreReadOnlyProperties = ignoreReadonlyProperties;

string expected = "{";
if (!ignoreReadonlyProperties)
expected += "\"latinName\":\"Animalia\",";
expected += "\"name\":\"Doggo\",\"isHungry\":false,";
expected += "\"weight\":1.5,";
expected += "\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"]}";
var dog = new DogListProperty
{
Name = "Doggo",
IsHungry = false,
Weight = 1.5,
FoodConsumed = { "kibble", "egg", "peanut butter" },
};
var actual = JsonSerializer.Serialize(dog, options);
Assert.AreEqual(expected, actual);
}

[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void DeserializeTest(bool ignoreReadonlyProperties, bool ignoreAdditionalProperties)
{
string serviceResponse = "{\"latinName\":\"Animalia\",\"weight\":1.5,\"name\":\"Doggo\",\"isHungry\":false,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"],\"numberOfLegs\":4}";

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new ModelJsonConverter(ignoreAdditionalProperties));
options.IgnoreReadOnlyProperties = ignoreReadonlyProperties;

var dog = JsonSerializer.Deserialize<DogListProperty>(serviceResponse, options);

Assert.AreEqual("Doggo", dog.Name);
Assert.AreEqual(false, dog.IsHungry);
Assert.AreEqual(1.5, dog.Weight);
CollectionAssert.AreEquivalent(new List<string> { "kibble", "egg", "peanut butter" }, dog.FoodConsumed);
Assert.AreEqual("Animalia", dog.LatinName);

var additionalProperties = typeof(Animal).GetProperty("RawData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(dog) as Dictionary<string, BinaryData>;
Assert.IsNotNull(additionalProperties);
Assert.AreEqual(!ignoreAdditionalProperties, additionalProperties.ContainsKey("numberOfLegs"));
if (!ignoreAdditionalProperties)
Assert.AreEqual("4", additionalProperties["numberOfLegs"].ToString());

string expected = "{";
if (!ignoreReadonlyProperties)
expected += "\"latinName\":\"Animalia\",";
expected += "\"name\":\"Doggo\",\"isHungry\":false,";
expected += "\"weight\":1.5,";
expected += "\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"]";
if (!ignoreAdditionalProperties)
expected += ",\"numberOfLegs\":4";
expected += "}";

var actual = JsonSerializer.Serialize(dog, options);
Assert.AreEqual(expected, actual);
}

[Test]
public void UsesMoreConcreteConverter()
{
string serviceResponse = "{\"latinName\":\"Animalia\",\"weight\":1.5,\"name\":\"Doggo\",\"isHungry\":false,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"],\"numberOfLegs\":4}";

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new ModelJsonConverter());
options.Converters.Add(new DogListPropertyBlankConverter());

//the more concrete converter should be used so we will validate the default values are used
var dog = JsonSerializer.Deserialize<DogListProperty>(serviceResponse, options);
Assert.AreEqual("Animal", dog.Name);
Assert.AreEqual(false, dog.IsHungry);
Assert.AreEqual(1.1d, dog.Weight);
CollectionAssert.AreEquivalent(new List<string>(), dog.FoodConsumed);
Assert.AreEqual("Animalia", dog.LatinName);

var additionalProperties = typeof(Animal).GetProperty("RawData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(dog) as Dictionary<string, BinaryData>;
Assert.IsNotNull(additionalProperties);
Assert.AreEqual(0, additionalProperties.Count);

var actual = JsonSerializer.Serialize(dog, options);
Assert.AreEqual("", actual);
}
}
}
Loading