private sealed partial class Emitter
{
// Literals in generated source
- private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
- private const string OptionsInstanceVariableName = "Options";
- private const string PropInitMethodNameSuffix = "PropInit";
- private const string CtorParamInitMethodNameSuffix = "CtorParamInit";
- private const string SerializeMethodNameSuffix = "Serialize";
private const string CreateValueInfoMethodName = "CreateValueInfo";
+ private const string CtorParamInitMethodNameSuffix = "CtorParamInit";
private const string DefaultOptionsStaticVarName = "s_defaultOptions";
private const string DefaultContextBackingStaticVarName = "s_defaultContext";
- private const string WriterVarName = "writer";
- private const string ValueVarName = "value";
+ internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory";
private const string JsonSerializerContextName = "JsonSerializerContext";
+ internal const string JsonContextVarName = "jsonContext";
+ private const string OptionsInstanceVariableName = "Options";
+ private const string PropInitMethodNameSuffix = "PropInit";
+ private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter";
+ private const string SerializeMethodNameSuffix = "Serialize";
+ private const string ValueVarName = "value";
+ private const string WriterVarName = "writer";
private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName();
private static readonly string s_generatedCodeAttributeSource = $@"
{
_currentContext = contextGenerationSpec;
+ bool generateGetConverterMethodForTypes = false;
+ bool generateGetConverterMethodForProperties = false;
+
foreach (TypeGenerationSpec typeGenerationSpec in _currentContext.RootSerializableTypes)
{
GenerateTypeInfo(typeGenerationSpec);
+
+ generateGetConverterMethodForTypes |= typeGenerationSpec.HasTypeFactoryConverter;
+ generateGetConverterMethodForProperties |= typeGenerationSpec.HasPropertyFactoryConverters;
}
string contextName = _currentContext.ContextType.Name;
// Add root context implementation.
- AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(), isRootContextDef: true);
+ AddSource(
+ $"{contextName}.g.cs",
+ GetRootJsonContextImplementation(generateGetConverterMethodForTypes, generateGetConverterMethodForProperties),
+ isRootContextDef: true);
// Add GetJsonTypeInfo override implementation.
AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation());
string typeCompilableName = typeMetadata.TypeRef;
string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
- string metadataInitSource;
-
// TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper.
+ StringBuilder metadataInitSource = new(
+ $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
+ {TypeTypeRef} typeToConvert = typeof({typeCompilableName});");
+
if (typeMetadata.IsValueType)
{
- metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
- {TypeTypeRef} typeToConvert = typeof({typeCompilableName});
- if (!converter.CanConvert(typeToConvert))
- {{
- {TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert);
- if (underlyingType != null && converter.CanConvert(underlyingType))
+ metadataInitSource.Append($@"
+ if (!converter.CanConvert(typeToConvert))
{{
- {JsonConverterTypeRef}? actualConverter = converter;
-
- if (converter is {JsonConverterFactoryTypeRef} converterFactory)
+ {TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert);
+ if (underlyingType != null && converter.CanConvert(underlyingType))
{{
- actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName});
-
- if (actualConverter == null || actualConverter is {JsonConverterFactoryTypeRef})
- {{
- throw new {InvalidOperationExceptionTypeRef}($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value."");
- }}
+ // Allow nullable handling to forward to the underlying type's converter.
+ converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName})!;
+ converter = (({ JsonConverterFactoryTypeRef })converter).CreateConverter(typeToConvert, { OptionsInstanceVariableName })!;
}}
-
- // Allow nullable handling to forward to the underlying type's converter.
- converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName});
- }}
- else
- {{
- throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
- }}
- }}
-
- _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
+ else
+ {{
+ throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
+ }}
+ }}");
}
else
{
- metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic};
- {TypeTypeRef} typeToConvert = typeof({typeCompilableName});
- if (!converter.CanConvert(typeToConvert))
- {{
- throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
- }}
-
- _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);";
+ metadataInitSource.Append($@"
+ if (!converter.CanConvert(typeToConvert))
+ {{
+ throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'."");
+ }}");
}
- return GenerateForType(typeMetadata, metadataInitSource);
+ metadataInitSource.Append($@"
+ _{typeFriendlyName} = { JsonMetadataServicesTypeRef }.{ GetCreateValueInfoMethodRef(typeCompilableName)} ({ OptionsInstanceVariableName}, converter); ");
+
+ return GenerateForType(typeMetadata, metadataInitSource.ToString());
}
private string GenerateForNullable(TypeGenerationSpec typeMetadata)
Type elementType = valueTypeGenerationSpec.Type;
string? writerMethodToCall = GetWriterMethod(elementType);
-
+
string iterationLogic;
string valueToWrite;
private string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenerationSpec)
{
const string PropVarName = "properties";
- const string JsonContextVarName = "jsonContext";
List<PropertyGenerationSpec> properties = typeGenerationSpec.PropertyGenSpecList!;
}}";
}
- private string GetRootJsonContextImplementation()
+ private string GetRootJsonContextImplementation(
+ bool generateGetConverterMethodForTypes,
+ bool generateGetConverterMethodForProperties)
{
string contextTypeRef = _currentContext.ContextTypeRef;
string contextTypeName = _currentContext.ContextType.Name;
{GetFetchLogicForRuntimeSpecifiedCustomConverter()}");
+ if (generateGetConverterMethodForProperties)
+ {
+ sb.Append(GetFetchLogicForGetCustomConverter_PropertiesWithFactories());
+ }
+
+ if (generateGetConverterMethodForProperties || generateGetConverterMethodForTypes)
+ {
+ sb.Append(GetFetchLogicForGetCustomConverter_TypesWithFactories());
+ }
+
return sb.ToString();
}
}}";
}
+ private string GetFetchLogicForGetCustomConverter_PropertiesWithFactories()
+ {
+ return @$"
+
+private {JsonConverterTypeRef}<T> {GetConverterFromFactoryMethodName}<T>({JsonConverterFactoryTypeRef} factory)
+{{
+ return ({JsonConverterTypeRef}<T>) {GetConverterFromFactoryMethodName}(typeof(T), factory);
+}}";
+ }
+
+ private string GetFetchLogicForGetCustomConverter_TypesWithFactories()
+ {
+ return @$"
+
+private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({TypeTypeRef} type, {JsonConverterFactoryTypeRef} factory)
+{{
+ {JsonConverterTypeRef}? converter = factory.CreateConverter(type, {Emitter.OptionsInstanceVariableName});
+ if (converter == null || converter is {JsonConverterFactoryTypeRef})
+ {{
+ throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance."");
+ }}
+
+ return converter;
+}}";
+ }
+
private string GetGetTypeInfoImplementation()
{
StringBuilder sb = new();
private const string SystemTextJsonNamespace = "System.Text.Json";
private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute";
private const string JsonElementFullName = "System.Text.Json.JsonElement";
+ private const string JsonConverterFactoryFullName = "System.Text.Json.Serialization.JsonConverterFactory";
private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute";
private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition";
private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute";
string? converterInstatiationLogic = null;
bool implementsIJsonOnSerialized = false;
bool implementsIJsonOnSerializing = false;
+ bool hasTypeFactoryConverter = false;
+ bool hasPropertyFactoryConverters = false;
IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(type);
foreach (CustomAttributeData attributeData in attributeDataList)
else if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null)
{
foundDesignTimeCustomConverter = true;
- converterInstatiationLogic = GetConverterInstantiationLogic(attributeData);
+ converterInstatiationLogic = GetConverterInstantiationLogic(
+ type,
+ attributeData,
+ forType: true,
+ ref hasTypeFactoryConverter);
}
}
for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
+ PropertyGenerationSpec spec;
+
foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
{
bool isVirtual = propertyInfo.IsVirtual();
continue;
}
- PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode);
- CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
- propertyOrderSpecified |= spec.Order != 0;
+ spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode);
+ CacheMemberHelper();
}
foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
continue;
}
- PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode);
+ spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode);
+ CacheMemberHelper();
+ }
+
+ void CacheMemberHelper()
+ {
CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
+
propertyOrderSpecified |= spec.Order != 0;
+ hasPropertyFactoryConverters |= spec.HasFactoryConverter;
+
+ // The property type may be implicitly in the context, so add that as well.
+ hasTypeFactoryConverter |= spec.TypeGenerationSpec.HasTypeFactoryConverter;
}
}
constructionStrategy,
nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec,
converterInstatiationLogic,
- implementsIJsonOnSerialized,
- implementsIJsonOnSerializing);
+ implementsIJsonOnSerialized : implementsIJsonOnSerialized,
+ implementsIJsonOnSerializing : implementsIJsonOnSerializing,
+ hasTypeFactoryConverter : hasTypeFactoryConverter,
+ hasPropertyFactoryConverters : hasPropertyFactoryConverters);
return typeMetadata;
}
private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode)
{
+ Type memberCLRType = GetMemberClrType(memberInfo);
IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
- bool hasJsonInclude = false;
- JsonIgnoreCondition? ignoreCondition = null;
- JsonNumberHandling? numberHandling = null;
- string? jsonPropertyName = null;
+ ProcessCustomAttributes(
+ attributeDataList,
+ memberCLRType,
+ out bool hasJsonInclude,
+ out string? jsonPropertyName,
+ out JsonIgnoreCondition? ignoreCondition,
+ out JsonNumberHandling? numberHandling,
+ out string? converterInstantiationLogic,
+ out int order,
+ out bool hasFactoryConverter);
+
+ ProcessMember(
+ memberInfo,
+ memberCLRType,
+ hasJsonInclude,
+ out bool isReadOnly,
+ out bool isPublic,
+ out bool canUseGetter,
+ out bool canUseSetter,
+ out bool getterIsVirtual,
+ out bool setterIsVirtual);
+
+ string clrName = memberInfo.Name;
+ string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy);
+ string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);
+
+ return new PropertyGenerationSpec
+ {
+ ClrName = clrName,
+ IsProperty = memberInfo.MemberType == MemberTypes.Property,
+ IsPublic = isPublic,
+ IsVirtual = isVirtual,
+ JsonPropertyName = jsonPropertyName,
+ RuntimePropertyName = runtimePropertyName,
+ PropertyNameVarName = propertyNameVarName,
+ IsReadOnly = isReadOnly,
+ CanUseGetter = canUseGetter,
+ CanUseSetter = canUseSetter,
+ GetterIsVirtual = getterIsVirtual,
+ SetterIsVirtual = setterIsVirtual,
+ DefaultIgnoreCondition = ignoreCondition,
+ NumberHandling = numberHandling,
+ Order = order,
+ HasJsonInclude = hasJsonInclude,
+ TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode),
+ DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(),
+ ConverterInstantiationLogic = converterInstantiationLogic,
+ HasFactoryConverter = hasFactoryConverter
+ };
+ }
+
+ private Type GetMemberClrType(MemberInfo memberInfo)
+ {
+ if (memberInfo is PropertyInfo propertyInfo)
+ {
+ return propertyInfo.PropertyType;
+ }
+
+ if (memberInfo is FieldInfo fieldInfo)
+ {
+ return fieldInfo.FieldType;
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private void ProcessCustomAttributes(
+ IList<CustomAttributeData> attributeDataList,
+ Type memberCLRType,
+ out bool hasJsonInclude,
+ out string? jsonPropertyName,
+ out JsonIgnoreCondition? ignoreCondition,
+ out JsonNumberHandling? numberHandling,
+ out string? converterInstantiationLogic,
+ out int order,
+ out bool hasFactoryConverter)
+ {
+ hasJsonInclude = false;
+ jsonPropertyName = null;
+ ignoreCondition = default;
+ numberHandling = default;
+ converterInstantiationLogic = null;
+ order = 0;
bool foundDesignTimeCustomConverter = false;
- string? converterInstantiationLogic = null;
- int order = 0;
+ hasFactoryConverter = false;
foreach (CustomAttributeData attributeData in attributeDataList)
{
if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null)
{
foundDesignTimeCustomConverter = true;
- converterInstantiationLogic = GetConverterInstantiationLogic(attributeData);
+ converterInstantiationLogic = GetConverterInstantiationLogic(
+ memberCLRType,
+ attributeData,
+ forType: false,
+ ref hasFactoryConverter);
}
else if (attributeType.Assembly.FullName == SystemTextJsonNamespace)
{
}
}
}
+ }
- Type memberCLRType;
- bool isReadOnly;
- bool isPublic = false;
- bool canUseGetter = false;
- bool canUseSetter = false;
- bool getterIsVirtual = false;
- bool setterIsVirtual = false;
+ private static void ProcessMember(
+ MemberInfo memberInfo,
+ Type memberClrType,
+ bool hasJsonInclude,
+ out bool isReadOnly,
+ out bool isPublic,
+ out bool canUseGetter,
+ out bool canUseSetter,
+ out bool getterIsVirtual,
+ out bool setterIsVirtual)
+ {
+ isPublic = false;
+ canUseGetter = false;
+ canUseSetter = false;
+ getterIsVirtual = false;
+ setterIsVirtual = false;
switch (memberInfo)
{
case PropertyInfo propertyInfo:
{
- memberCLRType = propertyInfo.PropertyType;
-
MethodInfo? getMethod = propertyInfo.GetMethod;
MethodInfo? setMethod = propertyInfo.SetMethod;
break;
case FieldInfo fieldInfo:
{
- memberCLRType = fieldInfo.FieldType;
isPublic = fieldInfo.IsPublic;
isReadOnly = fieldInfo.IsInitOnly;
default:
throw new InvalidOperationException();
}
-
- string clrName = memberInfo.Name;
- string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy);
- string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);
-
- return new PropertyGenerationSpec
- {
- ClrName = clrName,
- IsProperty = memberInfo.MemberType == MemberTypes.Property,
- IsPublic = isPublic,
- IsVirtual = isVirtual,
- JsonPropertyName = jsonPropertyName,
- RuntimePropertyName = runtimePropertyName,
- PropertyNameVarName = propertyNameVarName,
- IsReadOnly = isReadOnly,
- CanUseGetter = canUseGetter,
- CanUseSetter = canUseSetter,
- GetterIsVirtual = getterIsVirtual,
- SetterIsVirtual = setterIsVirtual,
- DefaultIgnoreCondition = ignoreCondition,
- NumberHandling = numberHandling,
- Order = order,
- HasJsonInclude = hasJsonInclude,
- TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode),
- DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(),
- ConverterInstantiationLogic = converterInstantiationLogic
- };
}
private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor)
=> accessor != null && (accessor.IsPublic || accessor.IsAssembly);
- private string? GetConverterInstantiationLogic(CustomAttributeData attributeData)
+ private string? GetConverterInstantiationLogic(
+ Type type, CustomAttributeData attributeData,
+ bool forType, // whether for a type or a property
+ ref bool hasFactoryConverter)
{
if (attributeData.AttributeType.FullName != JsonConverterAttributeFullName)
{
return null;
}
+ if (converterType.GetCompatibleBaseClass(JsonConverterFactoryFullName) != null)
+ {
+ hasFactoryConverter = true;
+
+ if (forType)
+ {
+ return $"{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())";
+ }
+ else
+ {
+ return $"{Emitter.JsonContextVarName}.{Emitter.GetConverterFromFactoryMethodName}<{type.GetCompilableName()}>(new {converterType.GetCompilableName()}())";
+ }
+ }
+
return $"new {converterType.GetCompilableName()}()";
}
/// Source code to instantiate design-time specified custom converter.
/// </summary>
public string? ConverterInstantiationLogic { get; init; }
+
+ public bool HasFactoryConverter { get; init; }
}
}
public string? ConverterInstantiationLogic { get; private set; }
+ // Only generate certain helper methods if necessary.
+ public bool HasPropertyFactoryConverters { get; private set; }
+ public bool HasTypeFactoryConverter { get; private set; }
+
public string FastPathSerializeMethodName
{
get
TypeGenerationSpec? nullableUnderlyingTypeMetadata,
string? converterInstantiationLogic,
bool implementsIJsonOnSerialized,
- bool implementsIJsonOnSerializing)
+ bool implementsIJsonOnSerializing,
+ bool hasTypeFactoryConverter,
+ bool hasPropertyFactoryConverters)
{
GenerationMode = generationMode;
TypeRef = type.GetCompilableName();
ConverterInstantiationLogic = converterInstantiationLogic;
ImplementsIJsonOnSerialized = implementsIJsonOnSerialized;
ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
+ HasTypeFactoryConverter = hasTypeFactoryConverter;
+ HasPropertyFactoryConverters = hasPropertyFactoryConverters;
}
public bool TryFilterSerializableProps(
public JsonTypeInfo<RealWorldContextTests.ClassWithEnumAndNullable> ClassWithEnumAndNullable { get; }
public JsonTypeInfo<ClassWithCustomConverter> ClassWithCustomConverter { get; }
public JsonTypeInfo<StructWithCustomConverter> StructWithCustomConverter { get; }
+ public JsonTypeInfo<ClassWithCustomConverterFactory> ClassWithCustomConverterFactory { get; }
+ public JsonTypeInfo<StructWithCustomConverterFactory> StructWithCustomConverterFactory { get; }
public JsonTypeInfo<ClassWithCustomConverterProperty> ClassWithCustomConverterProperty { get; }
public JsonTypeInfo<StructWithCustomConverterProperty> StructWithCustomConverterProperty { get; }
+ public JsonTypeInfo<ClassWithCustomConverterPropertyFactory> ClassWithCustomConverterPropertyFactory { get; }
+ public JsonTypeInfo<StructWithCustomConverterPropertyFactory> StructWithCustomConverterPropertyFactory { get; }
public JsonTypeInfo<ClassWithBadCustomConverter> ClassWithBadCustomConverter { get; }
public JsonTypeInfo<StructWithBadCustomConverter> StructWithBadCustomConverter { get; }
}
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
[JsonSerializable(typeof(ClassWithCustomConverter))]
[JsonSerializable(typeof(StructWithCustomConverter))]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory))]
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize);
Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize);
+ Assert.Null(MetadataAndSerializationContext.Default.SampleEnum.Serialize);
Assert.Null(MetadataAndSerializationContext.Default.String.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverter);
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverter);
+ Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterFactory);
+ Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterFactory);
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterProperty);
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterProperty);
+ Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterPropertyFactory);
+ Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterPropertyFactory);
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter);
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter);
}
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
- [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
- [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedClass.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.ObjectArray.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.SampleEnum.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.String.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize);
+ Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize);
}
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
[JsonSerializable(typeof(ClassWithCustomConverter))]
[JsonSerializable(typeof(StructWithCustomConverter))]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory))]
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
internal partial class MetadataContext : JsonSerializerContext, ITestContext
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
}
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum EnumWrittenAsString
+ {
+ A = 1
+ }
+
+ [JsonSerializable(typeof(EnumWrittenAsString))]
+ public partial class ContextWithExplicitStringEnum : JsonSerializerContext
+ {
+ }
+
+ public class PocoWithEnum
+ {
+ public EnumWrittenAsString MyEnum { get; set; }
+ }
+
+ [JsonSerializable(typeof(PocoWithEnum))]
+ public partial class ContextWithImplicitStringEnum : JsonSerializerContext
+ {
+ }
+
public sealed class MetadataContextTests : RealWorldContextTests
{
public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { }
Assert.Null(MetadataContext.Default.MyNestedClass.Serialize);
Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize);
Assert.Null(MetadataContext.Default.ObjectArray.Serialize);
+ Assert.Null(MetadataContext.Default.SampleEnum.Serialize);
Assert.Null(MetadataContext.Default.String.Serialize);
Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.Null(MetadataContext.Default.ClassWithCustomConverter.Serialize);
Assert.Null(MetadataContext.Default.StructWithCustomConverter.Serialize);
+ Assert.Null(MetadataContext.Default.ClassWithCustomConverterFactory.Serialize);
+ Assert.Null(MetadataContext.Default.StructWithCustomConverterFactory.Serialize);
Assert.Null(MetadataContext.Default.ClassWithCustomConverterProperty.Serialize);
Assert.Null(MetadataContext.Default.StructWithCustomConverterProperty.Serialize);
+ Assert.Null(MetadataContext.Default.ClassWithCustomConverterPropertyFactory.Serialize);
+ Assert.Null(MetadataContext.Default.StructWithCustomConverterPropertyFactory.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize);
}
+
+ [Fact]
+ public void EnsureHelperMethodGenerated_TypeFactory()
+ {
+ // There are 2 helper methods generated for obtaining a converter from a factory:
+ // - JsonConverter<T> version that is property-based (that calls the one below)
+ // - JsonConverter version that is Type-based
+ // and this test verifies the latter one is generated. Other tests also have property-level
+ // factories and thus verify both are created.
+
+ const string Json = "\"A\"";
+
+ EnumWrittenAsString obj = EnumWrittenAsString.A;
+
+ string json = JsonSerializer.Serialize(obj, ContextWithExplicitStringEnum.Default.EnumWrittenAsString);
+ Assert.Equal(Json, json);
+
+ obj = JsonSerializer.Deserialize(Json, ContextWithExplicitStringEnum.Default.EnumWrittenAsString);
+ Assert.Equal(EnumWrittenAsString.A, obj);
+ }
+
+ [Fact]
+ public void EnsureHelperMethodGenerated_ImplicitPropertyFactory()
+ {
+ // ContextWithImplicitStringEnum does not have an entry for EnumWrittenAsString since it is
+ // implictly added by PocoWithEnum. Verify helper methods are still being created properly.
+
+ const string Json = "{\"MyEnum\":\"A\"}";
+
+ PocoWithEnum obj = new() { MyEnum = EnumWrittenAsString.A };
+
+ string json = JsonSerializer.Serialize(obj, ContextWithImplicitStringEnum.Default.PocoWithEnum);
+ Assert.Equal(Json, json);
+
+ obj = JsonSerializer.Deserialize(Json, ContextWithImplicitStringEnum.Default.PocoWithEnum);
+ Assert.Equal(EnumWrittenAsString.A, obj.MyEnum);
+ }
}
}
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize);
Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize);
Assert.Null(MixedModeContext.Default.ObjectArray.Serialize);
+ Assert.Null(MixedModeContext.Default.SampleEnum.Serialize);
Assert.Null(MixedModeContext.Default.String.Serialize);
Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.Null(MixedModeContext.Default.ClassWithCustomConverter.Serialize);
Assert.Null(MixedModeContext.Default.StructWithCustomConverter.Serialize);
+ Assert.Null(MixedModeContext.Default.ClassWithCustomConverterFactory.Serialize);
+ Assert.Null(MixedModeContext.Default.StructWithCustomConverterFactory.Serialize);
Assert.Null(MixedModeContext.Default.ClassWithCustomConverterProperty.Serialize);
Assert.Null(MixedModeContext.Default.StructWithCustomConverterProperty.Serialize);
+ Assert.Null(MixedModeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize);
+ Assert.Null(MixedModeContext.Default.StructWithCustomConverterPropertyFactory.Serialize);
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.StructWithBadCustomConverter.Serialize);
}
-// Licensed to the .NET Foundation under one or more agreements.
+// 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;
}
[Fact]
+ public virtual void RoundTripWithCustomConverterFactory_Class()
+ {
+ const string Json = "{\"MyInt\":142}";
+
+ ClassWithCustomConverterFactory obj = new()
+ {
+ MyInt = 42
+ };
+
+ string json = JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterFactory);
+ Assert.Equal(Json, json);
+
+ obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterFactory);
+ Assert.Equal(42, obj.MyInt);
+ }
+
+ [Fact]
public virtual void RoundTripWithCustomConverter_Struct()
{
const string Json = "{\"MyInt\":142}";
}
[Fact]
+ public virtual void RoundTripWithCustomPropertyConverterFactory_Class()
+ {
+ const string Json = "{\"MyEnum\":\"A\"}";
+
+ ClassWithCustomConverterPropertyFactory obj = new()
+ {
+ MyEnum = SampleEnum.A
+ };
+
+ if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
+ {
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory));
+ }
+ else
+ {
+ string json = JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory);
+ Assert.Equal(Json, json);
+ }
+
+ if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
+ {
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory));
+ }
+ else
+ {
+ obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterPropertyFactory);
+ Assert.Equal(SampleEnum.A, obj.MyEnum);
+ }
+ }
+
+ [Fact]
+ public virtual void RoundTripWithCustomPropertyConverterFactory_Struct()
+ {
+ const string Json = "{\"MyEnum\":\"A\"}";
+
+ StructWithCustomConverterPropertyFactory obj = new()
+ {
+ MyEnum = SampleEnum.A
+ };
+
+ if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
+ {
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory));
+ }
+ else
+ {
+ string json = JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory);
+ Assert.Equal(Json, json);
+ }
+
+ if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
+ {
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory));
+ }
+ else
+ {
+ obj = JsonSerializer.Deserialize(Json, DefaultContext.StructWithCustomConverterPropertyFactory);
+ Assert.Equal(SampleEnum.A, obj.MyEnum);
+ }
+ }
+
+ [Fact]
public virtual void BadCustomConverter_Class()
{
const string Json = "{\"MyInt\":142}";
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
[JsonSerializable(typeof(ClassWithCustomConverter))]
[JsonSerializable(typeof(StructWithCustomConverter))]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory))]
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
internal partial class SerializationContext : JsonSerializerContext, ITestContext
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
+ [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.Null(SerializationContext.Default.ClassWithCustomConverter.Serialize);
Assert.Null(SerializationContext.Default.StructWithCustomConverter.Serialize);
+ Assert.Null(SerializationContext.Default.ClassWithCustomConverterFactory.Serialize);
+ Assert.Null(SerializationContext.Default.StructWithCustomConverterFactory.Serialize);
Assert.Null(SerializationContext.Default.ClassWithCustomConverterProperty.Serialize);
Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.Serialize);
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedClass.Serialize);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ObjectArray.Serialize);
+ Assert.Null(SerializationWithPerTypeAttributeContext.Default.SampleEnum.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.String.Serialize);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize);
+ Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize);
+ Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize);
+ Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize);
+ Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.Serialize);
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize);
}
<Compile Include="MetadataAndSerializationContextTests.cs" />
<Compile Include="MetadataContextTests.cs" />
<Compile Include="MixedModeContextTests.cs" />
+ <Compile Include="RealWorldContextTests.cs" />
<Compile Include="SerializationContextTests.cs" />
<Compile Include="SerializationLogicTests.cs" />
<Compile Include="Serialization\PropertyNameTests.cs" />
<Compile Include="Serialization\PropertyVisibilityTests.cs" />
<Compile Include="TestClasses.cs" />
- <Compile Include="RealWorldContextTests.cs" />
+ <Compile Include="TestClasses.CustomConverters.cs" />
</ItemGroup>
<Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text.Json.Serialization;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+ /// <summary>
+ /// Custom converter that adds\substract 100 from MyIntProperty.
+ /// </summary>
+ public class CustomConverter_ClassWithCustomConverter : JsonConverter<ClassWithCustomConverter>
+ {
+ public override ClassWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("No StartObject");
+ }
+
+ ClassWithCustomConverter obj = new();
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.PropertyName &&
+ reader.GetString() != "MyInt")
+ {
+ throw new JsonException("Wrong property name");
+ }
+
+ reader.Read();
+ obj.MyInt = reader.GetInt32() - 100;
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.EndObject)
+ {
+ throw new JsonException("No EndObject");
+ }
+
+ return obj;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ClassWithCustomConverter value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(ClassWithCustomConverter.MyInt), value.MyInt + 100);
+ writer.WriteEndObject();
+ }
+ }
+
+ /// <summary>
+ /// Custom converter that adds\substract 100 from MyIntProperty.
+ /// </summary>
+ public class CustomConverter_ClassWithCustomConverterFactory : JsonConverter<ClassWithCustomConverterFactory>
+ {
+ public override ClassWithCustomConverterFactory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("No StartObject");
+ }
+
+ ClassWithCustomConverterFactory obj = new();
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.PropertyName &&
+ reader.GetString() != "MyInt")
+ {
+ throw new JsonException("Wrong property name");
+ }
+
+ reader.Read();
+ obj.MyInt = reader.GetInt32() - 100;
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.EndObject)
+ {
+ throw new JsonException("No EndObject");
+ }
+
+ return obj;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ClassWithCustomConverterFactory value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(ClassWithCustomConverterFactory.MyInt), value.MyInt + 100);
+ writer.WriteEndObject();
+ }
+ }
+
+ /// <summary>
+ /// Custom converter that adds\substract 100 from MyIntProperty.
+ /// </summary>
+ public class CustomConverter_StructWithCustomConverter : JsonConverter<StructWithCustomConverter>
+ {
+ public override StructWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("No StartObject");
+ }
+
+ StructWithCustomConverter obj = new();
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.PropertyName &&
+ reader.GetString() != "MyInt")
+ {
+ throw new JsonException("Wrong property name");
+ }
+
+ reader.Read();
+ obj.MyInt = reader.GetInt32() - 100;
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.EndObject)
+ {
+ throw new JsonException("No EndObject");
+ }
+
+ return obj;
+ }
+
+ public override void Write(Utf8JsonWriter writer, StructWithCustomConverter value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(StructWithCustomConverter.MyInt), value.MyInt + 100);
+ writer.WriteEndObject();
+ }
+ }
+
+ /// <summary>
+ /// Custom converter that adds\substract 100 from MyIntProperty.
+ /// </summary>
+ public class CustomConverter_StructWithCustomConverterFactory : JsonConverter<StructWithCustomConverterFactory>
+ {
+ public override StructWithCustomConverterFactory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("No StartObject");
+ }
+
+ StructWithCustomConverterFactory obj = new();
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.PropertyName &&
+ reader.GetString() != "MyInt")
+ {
+ throw new JsonException("Wrong property name");
+ }
+
+ reader.Read();
+ obj.MyInt = reader.GetInt32() - 100;
+
+ reader.Read();
+ if (reader.TokenType != JsonTokenType.EndObject)
+ {
+ throw new JsonException("No EndObject");
+ }
+
+ return obj;
+ }
+
+ public override void Write(Utf8JsonWriter writer, StructWithCustomConverterFactory value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteNumber(nameof(StructWithCustomConverterFactory.MyInt), value.MyInt + 100);
+ writer.WriteEndObject();
+ }
+ }
+
+ public class CustomConverterFactory : JsonConverterFactory
+ {
+ public CustomConverterFactory()
+ {
+ }
+
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return (
+ typeToConvert == typeof(StructWithCustomConverterFactory) ||
+ typeToConvert == typeof(ClassWithCustomConverterFactory));
+ }
+
+ public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeToConvert == typeof(StructWithCustomConverterFactory))
+ {
+ return new CustomConverter_StructWithCustomConverterFactory();
+ }
+
+ if (typeToConvert == typeof(ClassWithCustomConverterFactory))
+ {
+ return new CustomConverter_ClassWithCustomConverterFactory();
+ }
+
+ throw new InvalidOperationException("Not expected.");
+ }
+ }
+
+ [JsonConverter(typeof(CustomConverter_ClassWithCustomConverter))]
+ public class ClassWithCustomConverter
+ {
+ public int MyInt { get; set; }
+ }
+
+ [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))]
+ public struct StructWithCustomConverter
+ {
+ public int MyInt { get; set; }
+ }
+
+ [JsonConverter(typeof(CustomConverterFactory))]
+ public class ClassWithCustomConverterFactory
+ {
+ public int MyInt { get; set; }
+ }
+
+ [JsonConverter(typeof(CustomConverterFactory))]
+ public struct StructWithCustomConverterFactory
+ {
+ public int MyInt { get; set; }
+ }
+
+ public class ClassWithCustomConverterProperty
+ {
+ [JsonConverter(typeof(NestedPocoCustomConverter))]
+ public NestedPoco Property { get; set; }
+
+ public class NestedPoco
+ {
+ public int Value { get; set; }
+ }
+
+ public class NestedPocoCustomConverter : JsonConverter<NestedPoco>
+ {
+ public override NestedPoco? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new NestedPoco { Value = reader.GetInt32() };
+ public override void Write(Utf8JsonWriter writer, NestedPoco value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
+ }
+ }
+
+ public struct StructWithCustomConverterProperty
+ {
+ [JsonConverter(typeof(ClassWithCustomConverterProperty.NestedPocoCustomConverter))]
+ public ClassWithCustomConverterProperty.NestedPoco Property { get; set; }
+ }
+
+ public struct ClassWithCustomConverterPropertyFactory
+ {
+ [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
+ public SampleEnum MyEnum { get; set; }
+ }
+
+ public struct StructWithCustomConverterPropertyFactory
+ {
+ [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
+ public SampleEnum MyEnum { get; set; }
+ }
+
+ public enum SampleEnum
+ {
+ A = 1,
+ B = 2
+ }
+
+ [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid
+ public class ClassWithBadCustomConverter
+ {
+ public int MyInt { get; set; }
+ }
+
+ [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid
+ public struct StructWithBadCustomConverter
+ {
+ public int MyInt { get; set; }
+ }
+}
}
internal struct MyStruct { }
-
- /// <summary>
- /// Custom converter that adds\substract 100 from MyIntProperty.
- /// </summary>
- public class CustomConverterForClass : JsonConverter<ClassWithCustomConverter>
- {
- public override ClassWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType != JsonTokenType.StartObject)
- {
- throw new JsonException("No StartObject");
- }
-
- ClassWithCustomConverter obj = new();
-
- reader.Read();
- if (reader.TokenType != JsonTokenType.PropertyName &&
- reader.GetString() != "MyInt")
- {
- throw new JsonException("Wrong property name");
- }
-
- reader.Read();
- obj.MyInt = reader.GetInt32() - 100;
-
- reader.Read();
- if (reader.TokenType != JsonTokenType.EndObject)
- {
- throw new JsonException("No EndObject");
- }
-
- return obj;
- }
-
- public override void Write(Utf8JsonWriter writer, ClassWithCustomConverter value, JsonSerializerOptions options)
- {
- writer.WriteStartObject();
- writer.WriteNumber(nameof(ClassWithCustomConverter.MyInt), value.MyInt + 100);
- writer.WriteEndObject();
- }
- }
-
- [JsonConverter(typeof(CustomConverterForClass))]
- public class ClassWithCustomConverter
- {
- public int MyInt { get; set; }
- }
-
- public class ClassWithCustomConverterProperty
- {
- [JsonConverter(typeof(NestedPocoCustomConverter))]
- public NestedPoco Property { get; set; }
-
- public class NestedPoco
- {
- public int Value { get; set; }
- }
-
- public class NestedPocoCustomConverter : JsonConverter<NestedPoco>
- {
- public override NestedPoco? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new NestedPoco { Value = reader.GetInt32() };
- public override void Write(Utf8JsonWriter writer, NestedPoco value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
- }
- }
-
- public struct StructWithCustomConverterProperty
- {
- [JsonConverter(typeof(ClassWithCustomConverterProperty.NestedPocoCustomConverter))]
- public ClassWithCustomConverterProperty.NestedPoco Property { get; set; }
- }
-
- [JsonConverter(typeof(CustomConverterForStruct))] // Invalid
- public class ClassWithBadCustomConverter
- {
- public int MyInt { get; set; }
- }
-
- /// <summary>
- /// Custom converter that adds\substract 100 from MyIntProperty.
- /// </summary>
- public class CustomConverterForStruct : JsonConverter<StructWithCustomConverter>
- {
- public override StructWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- if (reader.TokenType != JsonTokenType.StartObject)
- {
- throw new JsonException("No StartObject");
- }
-
- StructWithCustomConverter obj = new();
-
- reader.Read();
- if (reader.TokenType != JsonTokenType.PropertyName &&
- reader.GetString() != "MyInt")
- {
- throw new JsonException("Wrong property name");
- }
-
- reader.Read();
- obj.MyInt = reader.GetInt32() - 100;
-
- reader.Read();
- if (reader.TokenType != JsonTokenType.EndObject)
- {
- throw new JsonException("No EndObject");
- }
-
- return obj;
- }
-
- public override void Write(Utf8JsonWriter writer, StructWithCustomConverter value, JsonSerializerOptions options)
- {
- writer.WriteStartObject();
- writer.WriteNumber(nameof(StructWithCustomConverter.MyInt), value.MyInt + 100);
- writer.WriteEndObject();
- }
- }
-
- [JsonConverter(typeof(CustomConverterForStruct))]
- public struct StructWithCustomConverter
- {
- public int MyInt { get; set; }
- }
-
- [JsonConverter(typeof(CustomConverterForClass))] // Invalid
- public struct StructWithBadCustomConverter
- {
- public int MyInt { get; set; }
- }
}