Skip to content

Add acceptance tests for DataContract attributes & Specified pattern #11

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
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json.Serialization.Metadata;
using Xunit;

Expand Down Expand Up @@ -498,6 +502,125 @@ public static void CombineCustomResolverWithDefault()
Assert.Equal(originalObj.TestProperty, deserialized.TestProperty);
}

[Fact]
public static void DataContractResolverScenario()
{
var options = new JsonSerializerOptions { TypeInfoResolver = new DataContractResolver() };

var value = new DataContractResolver.TestClass { String = "str", Boolean = true, Int = 42, Ignored = "ignored" };
string json = JsonSerializer.Serialize(value, options);
Assert.Equal("""{"intValue":42,"boolValue":true,"stringValue":"str"}""", json);

DataContractResolver.TestClass result = JsonSerializer.Deserialize<DataContractResolver.TestClass>(json, options);
Assert.Equal("str", result.String);
Assert.Equal(42, result.Int);
Assert.True(result.Boolean);
}

internal class DataContractResolver : DefaultJsonTypeInfoResolver
{
[DataContract]
public class TestClass
{
[JsonIgnore] // ignored by the custom resolver
[DataMember(Name = "stringValue", Order = 2)]
public string String { get; set; }

[JsonPropertyName("BOOL_VALUE")] // ignored by the custom resolver
[DataMember(Name = "boolValue", Order = 1)]
public bool Boolean { get; set; }

[JsonPropertyOrder(int.MaxValue)] // ignored by the custom resolver
[DataMember(Name = "intValue", Order = 0)]
public int Int { get; set; }

[IgnoreDataMember]
public string Ignored { get; set; }
}

public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);

if (jsonTypeInfo.Kind == JsonTypeInfoKind.Object &&
type.GetCustomAttribute<DataContractAttribute>() is not null)
{
jsonTypeInfo.Properties.Clear();

IEnumerable<(PropertyInfo propInfo, DataMemberAttribute attr)> properties = type
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(propInfo => propInfo.GetCustomAttribute<IgnoreDataMemberAttribute>() is null)
.Select(propInfo => (propInfo, attr: propInfo.GetCustomAttribute<DataMemberAttribute>()))
.OrderBy(entry => entry.attr?.Order ?? 0);

foreach ((PropertyInfo propertyInfo, DataMemberAttribute? attr) in properties)
{
JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(propertyInfo.PropertyType, attr?.Name ?? propertyInfo.Name);
jsonPropertyInfo.Get =
propertyInfo.CanRead
? propertyInfo.GetValue
: null;

jsonPropertyInfo.Set = propertyInfo.CanWrite
? propertyInfo.SetValue
: null;

jsonTypeInfo.Properties.Add(jsonPropertyInfo);
}
}

return jsonTypeInfo;
}
}

[Fact]
public static void SpecifiedContractResolverScenario()
{
var options = new JsonSerializerOptions { TypeInfoResolver = new SpecifiedContractResolver() };

var value = new SpecifiedContractResolver.TestClass { String = "str", Int = 42 };
string json = JsonSerializer.Serialize(value, options);
Assert.Equal("""{}""", json);

value.IntSpecified = true;
json = JsonSerializer.Serialize(value, options);
Assert.Equal("""{"Int":42}""", json);

value.StringSpecified = true;
json = JsonSerializer.Serialize(value, options);
Assert.Equal("""{"String":"str","Int":42}""", json);
}

internal class SpecifiedContractResolver : DefaultJsonTypeInfoResolver
{
public class TestClass
{
public string String { get; set; }
[JsonIgnore]
public bool StringSpecified { get; set; }

public int Int { get; set; }
[JsonIgnore]
public bool IntSpecified { get; set; }
}
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);

foreach (JsonPropertyInfo property in jsonTypeInfo.Properties)
{
PropertyInfo? specifiedProperty = type.GetProperty(property.Name + "Specified", BindingFlags.Instance | BindingFlags.Public);

if (specifiedProperty != null && specifiedProperty.CanRead && specifiedProperty.PropertyType == typeof(bool))
{
property.ShouldSerialize = (obj, _) => (bool)specifiedProperty.GetValue(obj);
}
}

return jsonTypeInfo;
}
}

internal class TestClass
{
public int TestProperty { get; set; }
Expand Down