Skip to content
Merged
27 changes: 9 additions & 18 deletions sdk/core/Azure.Core/src/Serialization/ModelJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,16 @@ public class ModelJsonConverter : JsonConverter<IModelSerializable>
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
/// <summary>
/// .
/// </summary>
public bool IgnoreAdditionalProperties { get; set; } = true;

/// <summary>
/// .
/// String that determines Format of serialized model. "D" = data format which means both properties are false, "W" = wire format which means both properties are true Default is "D".
/// </summary>
public ModelJsonConverter() { }
public string Format { get; }
Comment thread
nisha-bhatia marked this conversation as resolved.
Outdated

/// <summary>
/// .
/// String that determines Format of serialized model. "D" = data format which means both properties are false, "W" = wire format which means both properties are true Default is "D".
/// </summary>
/// <param name="ignoreAdditionalProperties"></param>
public ModelJsonConverter(bool ignoreAdditionalProperties)
public ModelJsonConverter(string format = "D")
{
IgnoreAdditionalProperties = ignoreAdditionalProperties;
Format = format;
}

/// <summary>
Expand All @@ -57,7 +51,7 @@ public override bool CanConvert(Type typeToConvert)
public override IModelSerializable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
return (IModelSerializable)ModelSerializer.Deserialize(BinaryData.FromString(JsonDocument.ParseValue(ref reader).RootElement.GetRawText()), typeToConvert, ConvertOptions(options));
return (IModelSerializable)ModelSerializer.Deserialize(BinaryData.FromString(JsonDocument.ParseValue(ref reader).RootElement.GetRawText()), typeToConvert, GetOptions());
}

/// <summary>
Expand All @@ -70,20 +64,17 @@ public override IModelSerializable Read(ref Utf8JsonReader reader, Type typeToCo
public override void Write(Utf8JsonWriter writer, IModelSerializable value, JsonSerializerOptions options)
#pragma warning restore AZC0014 // Avoid using banned types in public API
{
BinaryData data = value.Serialize(ConvertOptions(options));
BinaryData data = value.Serialize(GetOptions());
#if NET6_0_OR_GREATER
writer.WriteRawValue(data);
#else
JsonSerializer.Serialize(writer, JsonDocument.Parse(data.ToString()).RootElement);
#endif
}

private ModelSerializerOptions ConvertOptions(JsonSerializerOptions options)
private ModelSerializerOptions GetOptions()
{
ModelSerializerOptions serializableOptions = new ModelSerializerOptions();
serializableOptions.IgnoreAdditionalProperties = IgnoreAdditionalProperties;
serializableOptions.IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties;
return serializableOptions;
return new ModelSerializerOptions(Format);
}
}
}
22 changes: 18 additions & 4 deletions sdk/core/Azure.Core/src/Serialization/ModelSerializerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ namespace Azure.Core.Serialization
public class ModelSerializerOptions
{
/// <summary>
/// Bool that determines if ReadOnlyProperties will be serialized. Default is false.
/// Consructor for ModelSerializerOptions. Takes in a string that determines Format of serialized model. "D" = data format which means IgnoreReadOnly and IgnoreAdditionalProperties are false, "W" = wire format which means both properties are true. Default is "D".
Comment thread
nisha-bhatia marked this conversation as resolved.
Outdated
/// </summary>
public bool IgnoreReadOnlyProperties { get; set; }
/// <param name="format"></param>
public ModelSerializerOptions(string format = "D")
{
//throw ArgumentException if not "D" or "W"
Format = ValidateFormat(format);
}

/// <summary>
/// Bool that determines if AdditionalProperties will be serialized. Default is false.
/// String that determines Format of serialized model. "D" = data format which means IgnoreReadOnly and IgnoreAdditionalProperties are false, "W" = wire format which means both properties are true. Default is "D".
/// </summary>
public bool IgnoreAdditionalProperties { get; set; }
public string Format { get; }

/// <summary>
/// Bool that determines if Json will be PrettyPrinted. Default is false.
Expand All @@ -35,5 +40,14 @@ public class ModelSerializerOptions
/// NameHint for Xml Models
/// </summary>
public string? NameHint { get; set; }

private string ValidateFormat(string x)
{
if (x != "D" && x != "W")
Comment thread
nisha-bhatia marked this conversation as resolved.
Outdated
{
throw new ArgumentException("Format must be either 'D' or 'W'.");
}
return x;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests
{
internal class CombinedInterfaceTests
{
[TestCase(true)]
[TestCase(false)]
public void CanRoundTripFutureVersionWithoutLossXml(bool ignoreReadOnlyProperties)
[TestCase("D")]
[TestCase("W")]
public void CanRoundTripFutureVersionWithoutLossXml(string format)
{
ModelSerializerOptions options = new ModelSerializerOptions();
options.IgnoreReadOnlyProperties = ignoreReadOnlyProperties;
ModelSerializerOptions options = new ModelSerializerOptions(format);

string serviceResponse =
"<Tag>" +
Expand All @@ -28,7 +27,7 @@ public void CanRoundTripFutureVersionWithoutLossXml(bool ignoreReadOnlyPropertie
"</Tag>";

var expectedSerializedString = "\uFEFF<?xml version=\"1.0\" encoding=\"utf-8\"?><Tag><Key>Color</Key><Value>Red</Value>";
if (!ignoreReadOnlyProperties)
if (format == "D")
expectedSerializedString += "<ReadOnlyProperty>ReadOnly</ReadOnlyProperty>";
expectedSerializedString += "</Tag>";

Expand All @@ -43,26 +42,23 @@ public void CanRoundTripFutureVersionWithoutLossXml(bool ignoreReadOnlyPropertie
Assert.That(roundTrip, Is.EqualTo(expectedSerializedString));

XmlModelForCombinedInterface model2 = ModelSerializer.Deserialize<XmlModelForCombinedInterface>(new BinaryData(Encoding.UTF8.GetBytes(roundTrip)), options);
VerifyModelXmlModelForCombinedInterface(model, model2, ignoreReadOnlyProperties);
VerifyModelXmlModelForCombinedInterface(model, model2, format);
}

[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void CanRoundTripFutureVersionWithoutLossJson(bool ignoreReadOnlyProperties, bool ignoreAdditionalProperties)
[TestCase("D")]
[TestCase("W")]
public void CanRoundTripFutureVersionWithoutLossJson(string format)
{
ModelSerializerOptions options = new ModelSerializerOptions();
options.IgnoreAdditionalProperties = ignoreAdditionalProperties;
options.IgnoreReadOnlyProperties = ignoreReadOnlyProperties;
ModelSerializerOptions options = new ModelSerializerOptions(format);

string serviceResponse = "{\"key\":\"Color\",\"value\":\"Red\",\"readOnlyProperty\":\"ReadOnly\",\"x\":\"extra\"}";

var expectedSerializedString = "{\"key\":\"Color\",\"value\":\"Red\"";
if (!ignoreReadOnlyProperties)
if (format == "D")
{
expectedSerializedString += ",\"readOnlyProperty\":\"ReadOnly\"";
if (!ignoreAdditionalProperties)
expectedSerializedString += ",\"x\":\"extra\"";
}
expectedSerializedString += "}";

JsonModelForCombinedInterface model = ModelSerializer.Deserialize<JsonModelForCombinedInterface>(new BinaryData(Encoding.UTF8.GetBytes(serviceResponse)), options);
Expand All @@ -72,35 +68,35 @@ public void CanRoundTripFutureVersionWithoutLossJson(bool ignoreReadOnlyProperti
Assert.AreEqual("ReadOnly", model.ReadOnlyProperty);
var additionalProperties = typeof(JsonModelForCombinedInterface).GetProperty("RawData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(model) as Dictionary<string, BinaryData>;
Assert.IsNotNull(additionalProperties);
Assert.AreEqual(!ignoreAdditionalProperties, additionalProperties.ContainsKey("x"));
Assert.AreEqual(format == "D", additionalProperties.ContainsKey("x"));
var data = ModelSerializer.Serialize(model, options);
string roundTrip = data.ToString();

Assert.That(roundTrip, Is.EqualTo(expectedSerializedString));

JsonModelForCombinedInterface model2 = ModelSerializer.Deserialize<JsonModelForCombinedInterface>(new BinaryData(Encoding.UTF8.GetBytes(roundTrip)), options);
VerifyModelJsonModelForCombinedInterface(model, model2, ignoreReadOnlyProperties, ignoreAdditionalProperties);
VerifyModelJsonModelForCombinedInterface(model, model2, format);
}

internal static void VerifyModelXmlModelForCombinedInterface(XmlModelForCombinedInterface expected, XmlModelForCombinedInterface actual, bool ignoreReadOnlyProperties)
internal static void VerifyModelXmlModelForCombinedInterface(XmlModelForCombinedInterface expected, XmlModelForCombinedInterface actual, string format)
{
Assert.AreEqual(expected.Key, actual.Key);
Assert.AreEqual(expected.Value, actual.Value);
if (!ignoreReadOnlyProperties)
if (format == "D")
Assert.AreEqual(expected.ReadOnlyProperty, actual.ReadOnlyProperty);
}

internal static void VerifyModelJsonModelForCombinedInterface(JsonModelForCombinedInterface expected, JsonModelForCombinedInterface actual, bool ignoreReadOnlyProperties, bool ignoreAdditionalProperties)
internal static void VerifyModelJsonModelForCombinedInterface(JsonModelForCombinedInterface expected, JsonModelForCombinedInterface actual, string format)
{
Assert.AreEqual(expected.Key, actual.Key);
Assert.AreEqual(expected.Value, actual.Value);
if (!ignoreReadOnlyProperties)
if (format == "D")
Assert.AreEqual(expected.ReadOnlyProperty, actual.ReadOnlyProperty);
var rawDataProperty = typeof(JsonModelForCombinedInterface).GetProperty("RawData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var expectedRawData = rawDataProperty.GetValue(expected) as Dictionary<string, BinaryData>;
var actualRawData = rawDataProperty.GetValue(actual) as Dictionary<string, BinaryData>;
Assert.AreEqual(expectedRawData.Count, actualRawData.Count);
if (!ignoreAdditionalProperties)
if (format == "D")
Assert.AreEqual(expectedRawData["x"].ToString(), actualRawData["x"].ToString());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ BinaryData IModelSerializable.Serialize(ModelSerializerOptions options)
MemoryStream stream = new MemoryStream();
Utf8JsonWriter writer = new Utf8JsonWriter(stream);
writer.WriteStartObject();
if (!options.IgnoreReadOnlyProperties)
if (options.Format == "D")
{
writer.WritePropertyName("readOnlyProperty"u8);
writer.WriteStringValue(ReadOnlyProperty);
Expand All @@ -70,7 +70,7 @@ BinaryData IModelSerializable.Serialize(ModelSerializerOptions options)
writer.WritePropertyName("modelC"u8);
SerializeT(writer, options);

if (!options.IgnoreAdditionalProperties)
if (options.Format == "D")
{
//write out the raw data
foreach (var property in RawData)
Expand Down Expand Up @@ -114,7 +114,7 @@ internal static Envelope<T> DeserializeEnvelope(JsonElement element, ModelSerial
continue;
}

if (!options.IgnoreAdditionalProperties)
if (options.Format == "D")
{
//this means it's an modelC property we got
rawData.Add(property.Name, BinaryData.FromString(property.Value.GetRawText()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests
{
internal class EnvelopeTests
{
private readonly ModelSerializerOptions _wireOptions = new ModelSerializerOptions { IgnoreReadOnlyProperties = false };
private readonly ModelSerializerOptions _objectOptions = new ModelSerializerOptions();

[TestCase(true)]
[TestCase(false)]
public void CanRoundTripFutureVersionWithoutLoss(bool ignoreReadOnly)
[TestCase("D")]
[TestCase("W")]
public void CanRoundTripFutureVersionWithoutLoss(string format)
{
string serviceResponse =
"{\"readOnlyProperty\":\"read\"," +
Expand All @@ -26,12 +23,12 @@ public void CanRoundTripFutureVersionWithoutLoss(bool ignoreReadOnly)
"}";

StringBuilder expectedSerialized = new StringBuilder("{");
if (!ignoreReadOnly)
if (format == "D")
{
expectedSerialized.Append("\"readOnlyProperty\":\"read\",");
}
expectedSerialized.Append("\"modelA\":{");
if (!ignoreReadOnly)
if (format == "D")
{
expectedSerialized.Append("\"latinName\":\"Felis catus\",\"hasWhiskers\":false,");
}
Expand All @@ -40,9 +37,9 @@ public void CanRoundTripFutureVersionWithoutLoss(bool ignoreReadOnly)
expectedSerialized.Append("}");
var expectedSerializedString = expectedSerialized.ToString();

ModelSerializerOptions options = new ModelSerializerOptions() { IgnoreReadOnlyProperties = ignoreReadOnly };
ModelSerializerOptions options = new ModelSerializerOptions(format);

if (ignoreReadOnly)
if (format == "W")
{
JsonSerializerSettings settings = new JsonSerializerSettings
{
Expand All @@ -55,7 +52,7 @@ public void CanRoundTripFutureVersionWithoutLoss(bool ignoreReadOnly)

Envelope<ModelC> model = ModelSerializer.Deserialize<Envelope<ModelC>>(new BinaryData(Encoding.UTF8.GetBytes(serviceResponse)), options: options);

if (!ignoreReadOnly)
if (format == "D")
{
Assert.That(model.ReadOnlyProperty, Is.EqualTo("read"));
}
Expand Down
Loading