using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public int StreetNumber { get; set; }
public string Name { get; set; }
public string Id { get; set; }
public AddressDto Address { get; set; }
public string SomeOtherValue { get; set; }
public AddressDto SecretAddress { get; set; }
[JsonConverter(typeof(ValueConverter<AddressDto>))]
public ValueDto<AddressDto> PublicAddress { get; set; }
public T Value { get; set; }
public class ValueConverter<T> : JsonConverter<ValueDto<T>>
public override void WriteJson(JsonWriter writer, ValueDto<T> value, JsonSerializer serializer) => serializer.Serialize(writer, value == null ? null : value.Value);
public override ValueDto<T> ReadJson(JsonReader reader, Type objectType, ValueDto<T> existingValue, bool hasExistingValue, JsonSerializer serializer) => new ValueDto<T> { Value = serializer.Deserialize<T>(reader) };
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ShouldSerializeAttribute : System.Attribute
public class ShouldSerializeContractResolver: DefaultContractResolver
static ThreadLocal<bool> inShouldSerialize = new (() => false);
static bool InShouldSerialize { get => inShouldSerialize.Value; set => inShouldSerialize.Value = value; }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
var property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeAttribute>(inherit: false);
var old = property.ShouldSerialize;
property.ShouldSerialize = instance => InShouldSerialize && (old == null || old(instance));
var old = property.Converter;
property.Converter = new InShouldSerializeConverter();
property.Converter = new InShouldSerializeConverterDecorator(old);
class InShouldSerializeConverter : JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var old = InShouldSerialize;
InShouldSerialize = true;
serializer.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanRead => false;
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
class InShouldSerializeConverterDecorator : JsonConverter
readonly JsonConverter innerConverter;
public InShouldSerializeConverterDecorator(JsonConverter innerConverter) => this.innerConverter = innerConverter ?? throw new ArgumentNullException(nameof(innerConverter));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var old = InShouldSerialize;
InShouldSerialize = true;
if (innerConverter.CanWrite)
innerConverter.WriteJson(writer, value, serializer);
serializer.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var old = InShouldSerialize;
InShouldSerialize = true;
if (innerConverter.CanRead)
return innerConverter.ReadJson(reader, objectType, existingValue, serializer);
return serializer.Deserialize(reader, objectType);
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
public static void Test()
public static void TestAddress()
var address = new AddressDto
var person = new PersonDto
Name = "ShouldNotAppear",
var otherDto = new SomeOtherDto
SomeOtherValue = "ShouldAppear",
IContractResolver resolver = new ShouldSerializeContractResolver();
var settings = new JsonSerializerSettings
ContractResolver = resolver,
var json = JsonConvert.SerializeObject(person, Formatting.Indented, settings);
Console.WriteLine("Serializing {0}:", person);
var person2 = JsonConvert.DeserializeObject<PersonDto>(json, settings);
Assert.AreEqual(person.Id, person2.Id);
Assert.AreEqual(person.Address?.StreetNumber, person2.Address?.StreetNumber);
Assert.AreNotEqual(person.Name, person2.Name);
Assert.IsTrue(!json.Contains("ShouldNotAppear"));
public static void TestOther()
var address = new AddressDto
var publicAddress = new AddressDto
var otherDto = new SomeOtherDto
SomeOtherValue = "ShouldAppear",
PublicAddress = new ValueDto<AddressDto> { Value = publicAddress },
IContractResolver resolver = new ShouldSerializeContractResolver();
var settings = new JsonSerializerSettings
ContractResolver = resolver,
var json2 = JsonConvert.SerializeObject(otherDto, Formatting.Indented, settings);
Console.WriteLine("Serializing {0}:", otherDto);
Console.WriteLine(json2);
var other2 = JsonConvert.DeserializeObject<SomeOtherDto>(json2, settings);
Assert.AreEqual(otherDto.SomeOtherValue, other2.SomeOtherValue);
Assert.AreNotEqual(otherDto.SecretAddress?.StreetNumber, other2.SecretAddress?.StreetNumber);
Assert.IsTrue(!json2.Contains("ShouldNotAppear"));
Assert.AreEqual(otherDto.PublicAddress.Value.StreetNumber, other2.PublicAddress.Value.StreetNumber);
public static partial class JsonExtensions
public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) =>
reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");