-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
System.Text.Json source generation and records from another assembly #62374
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue DetailsThis issue has been moved from a ticket on Developer Community. Hi, I'm trying to switch from Newtonsoft to System.Text.Json with source generation. using System;
using System.Runtime.CompilerServices;
namespace ClassLibrary1
{
public record Person
{
public Person(string name) => Name = name;
public string Name { get; }
}
} and an executable with the following code, using System.Text.Json source generation: using System.Text.Json;
using System.Text.Json.Serialization;
using ClassLibrary1;
using static System.Console;
var context = new Context();
var value = new Person("class");
var serialized = JsonSerializer.Serialize(value, typeof(Person), context);
WriteLine(serialized);
var unserialized = (Person) JsonSerializer.Deserialize(serialized, typeof(Person), context);
[JsonSerializable(typeof(Person))]
partial class Context : JsonSerializerContext
{
} The output of the WriteLine is
and then deserialization fails with the following error as it does not find the name:
Switching the type of I tried to reproduce by the generated code of the record (IEquatable, ToString, ...) manually in the class, but I can't reproduce the record issue. By the way, if I look at the generated code in my Visual Studio private static void PersonSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::ClassLibrary1.Person? value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString(PropName_Name, value.Name);
writer.WriteEndObject();
} But if I look at the disassembly of my executable (I'm using dotPeek), I see why my serialized object is empty private static void PersonSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::ClassLibrary1.Person? value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteEndObject();
} I attached the project, it's fairly simple. Thank you Original Comments(no comments) Original Solutions(no solutions)
|
This seems to specifically impact record types specified in other assemblies. I'm not sure why the property ends up being skipped, the generated source appears to be identical for both classes and records: // <auto-generated/>
#nullable enable
partial class Context
{
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::ClassLibrary1.Person>? _Person;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::ClassLibrary1.Person> Person
{
get
{
if (_Person == null)
{
global::System.Text.Json.Serialization.JsonConverter? customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(global::ClassLibrary1.Person))) != null)
{
_Person = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::ClassLibrary1.Person>(Options, customConverter);
}
else
{
global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::ClassLibrary1.Person> objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::ClassLibrary1.Person>()
{
ObjectCreator = null,
ObjectWithParameterizedConstructorCreator = static (args) => new global::ClassLibrary1.Person((global::System.String)args[0]),
PropertyMetadataInitializer = PersonPropInit,
ConstructorParameterMetadataInitializer = PersonCtorParamInit,
NumberHandling = default,
SerializeHandler = PersonSerializeHandler
};
_Person = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo<global::ClassLibrary1.Person>(Options, objectInfo);
}
}
return _Person;
}
}
private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] PersonPropInit(global::System.Text.Json.Serialization.JsonSerializerContext context)
{
global::Context jsonContext = (global::Context)context;
global::System.Text.Json.JsonSerializerOptions options = context.Options;
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[1];
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.String> info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.String>()
{
IsProperty = true,
IsPublic = true,
IsVirtual = false,
DeclaringType = typeof(global::ClassLibrary1.Person),
PropertyTypeInfo = jsonContext.String,
Converter = null,
Getter = static (obj) => ((global::ClassLibrary1.Person)obj).Name!,
Setter = static (obj, value) => ((global::ClassLibrary1.Person)obj).Name = value!,
IgnoreCondition = null,
HasJsonInclude = false,
IsExtensionData = false,
NumberHandling = default,
PropertyName = "Name",
JsonPropertyName = null
};
properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.String>(options, info0);
return properties;
}
private static void PersonSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::ClassLibrary1.Person? value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString(PropName_Name, value.Name);
writer.WriteEndObject();
}
private static global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] PersonCtorParamInit()
{
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] parameters = new global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[1];
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues info;
info = new()
{
Name = "name",
ParameterType = typeof(global::System.String),
Position = 0,
HasDefaultValue = false,
DefaultValue = default(global::System.String)
};
parameters[0] = info;
return parameters;
}
} cc @layomia |
This causes silent data loss and is a candidate for a 6.0 servicing fix. |
I also ran into this issue. If I create a record in the base assembly everything works. If I copy that record to another assembly and reference it, it doesn't generate it correctly. Maybe the output helps. This is my class from the base assembly: public sealed record Meal2(Guid Id, Guid RecipeId, string RecipeTitle, DateTime MealDate); And this is the copy in the referenced assembly: public sealed record Meal(Guid Id, Guid RecipeId, string RecipeTitle, DateTime MealDate); Output of Meal2 source generation // <auto-generated/>
#nullable enable
partial class MessageSerializerContext
{
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::Dark.Model.Meal2>? _Meal2;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::Dark.Model.Meal2> Meal2
{
get
{
if (_Meal2 == null)
{
global::System.Text.Json.Serialization.JsonConverter? customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(global::Dark.Model.Meal2))) != null)
{
_Meal2 = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::Dark.Model.Meal2>(Options, customConverter);
}
else
{
global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::Dark.Model.Meal2> objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::Dark.Model.Meal2>()
{
ObjectCreator = null,
ObjectWithParameterizedConstructorCreator = static (args) => new global::Dark.Model.Meal2((global::System.Guid)args[0], (global::System.Guid)args[1], (global::System.String)args[2], (global::System.DateTime)args[3]),
PropertyMetadataInitializer = Meal2PropInit,
ConstructorParameterMetadataInitializer = Meal2CtorParamInit,
NumberHandling = default,
SerializeHandler = Meal2SerializeHandler
};
_Meal2 = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo<global::Dark.Model.Meal2>(Options, objectInfo);
}
}
return _Meal2;
}
}
private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] Meal2PropInit(global::System.Text.Json.Serialization.JsonSerializerContext context)
{
global::MessageSerializerContext jsonContext = (global::MessageSerializerContext)context;
global::System.Text.Json.JsonSerializerOptions options = context.Options;
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] properties = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[4];
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.Guid> info0 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.Guid>()
{
IsProperty = true,
IsPublic = true,
IsVirtual = false,
DeclaringType = typeof(global::Dark.Model.Meal2),
PropertyTypeInfo = jsonContext.Guid,
Converter = null,
Getter = static (obj) => ((global::Dark.Model.Meal2)obj).Id,
Setter = static (obj, value) => throw new global::System.InvalidOperationException("Deserialization of init-only properties is currently not supported in source generation mode."),
IgnoreCondition = null,
HasJsonInclude = false,
IsExtensionData = false,
NumberHandling = default,
PropertyName = "Id",
JsonPropertyName = null
};
properties[0] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.Guid>(options, info0);
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.Guid> info1 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.Guid>()
{
IsProperty = true,
IsPublic = true,
IsVirtual = false,
DeclaringType = typeof(global::Dark.Model.Meal2),
PropertyTypeInfo = jsonContext.Guid,
Converter = null,
Getter = static (obj) => ((global::Dark.Model.Meal2)obj).RecipeId,
Setter = static (obj, value) => throw new global::System.InvalidOperationException("Deserialization of init-only properties is currently not supported in source generation mode."),
IgnoreCondition = null,
HasJsonInclude = false,
IsExtensionData = false,
NumberHandling = default,
PropertyName = "RecipeId",
JsonPropertyName = null
};
properties[1] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.Guid>(options, info1);
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.String> info2 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.String>()
{
IsProperty = true,
IsPublic = true,
IsVirtual = false,
DeclaringType = typeof(global::Dark.Model.Meal2),
PropertyTypeInfo = jsonContext.String,
Converter = null,
Getter = static (obj) => ((global::Dark.Model.Meal2)obj).RecipeTitle!,
Setter = static (obj, value) => throw new global::System.InvalidOperationException("Deserialization of init-only properties is currently not supported in source generation mode."),
IgnoreCondition = null,
HasJsonInclude = false,
IsExtensionData = false,
NumberHandling = default,
PropertyName = "RecipeTitle",
JsonPropertyName = null
};
properties[2] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.String>(options, info2);
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.DateTime> info3 = new global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues<global::System.DateTime>()
{
IsProperty = true,
IsPublic = true,
IsVirtual = false,
DeclaringType = typeof(global::Dark.Model.Meal2),
PropertyTypeInfo = jsonContext.DateTime,
Converter = null,
Getter = static (obj) => ((global::Dark.Model.Meal2)obj).MealDate,
Setter = static (obj, value) => throw new global::System.InvalidOperationException("Deserialization of init-only properties is currently not supported in source generation mode."),
IgnoreCondition = null,
HasJsonInclude = false,
IsExtensionData = false,
NumberHandling = default,
PropertyName = "MealDate",
JsonPropertyName = null
};
properties[3] = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreatePropertyInfo<global::System.DateTime>(options, info3);
return properties;
}
private static void Meal2SerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::Dark.Model.Meal2? value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteString(PropName_Id, value.Id);
writer.WriteString(PropName_RecipeId, value.RecipeId);
writer.WriteString(PropName_RecipeTitle, value.RecipeTitle);
writer.WriteString(PropName_MealDate, value.MealDate);
writer.WriteEndObject();
}
private static global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] Meal2CtorParamInit()
{
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] parameters = new global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[4];
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues info;
info = new()
{
Name = "Id",
ParameterType = typeof(global::System.Guid),
Position = 0,
HasDefaultValue = false,
DefaultValue = default(global::System.Guid)
};
parameters[0] = info;
info = new()
{
Name = "RecipeId",
ParameterType = typeof(global::System.Guid),
Position = 1,
HasDefaultValue = false,
DefaultValue = default(global::System.Guid)
};
parameters[1] = info;
info = new()
{
Name = "RecipeTitle",
ParameterType = typeof(global::System.String),
Position = 2,
HasDefaultValue = false,
DefaultValue = default(global::System.String)
};
parameters[2] = info;
info = new()
{
Name = "MealDate",
ParameterType = typeof(global::System.DateTime),
Position = 3,
HasDefaultValue = false,
DefaultValue = default(global::System.DateTime)
};
parameters[3] = info;
return parameters;
}
} Wrong Output of Meal source generation // <auto-generated/>
#nullable enable
partial class MessageSerializerContext
{
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::Dark.Model.Meal>? _Meal;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::Dark.Model.Meal> Meal
{
get
{
if (_Meal == null)
{
global::System.Text.Json.Serialization.JsonConverter? customConverter;
if (Options.Converters.Count > 0 && (customConverter = GetRuntimeProvidedCustomConverter(typeof(global::Dark.Model.Meal))) != null)
{
_Meal = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateValueInfo<global::Dark.Model.Meal>(Options, customConverter);
}
else
{
global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::Dark.Model.Meal> objectInfo = new global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<global::Dark.Model.Meal>()
{
ObjectCreator = null,
ObjectWithParameterizedConstructorCreator = static (args) => new global::Dark.Model.Meal((global::System.Guid)args[0], (global::System.Guid)args[1], (global::System.String)args[2], (global::System.DateTime)args[3]),
PropertyMetadataInitializer = MealPropInit,
ConstructorParameterMetadataInitializer = MealCtorParamInit,
NumberHandling = default,
SerializeHandler = MealSerializeHandler
};
_Meal = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo<global::Dark.Model.Meal>(Options, objectInfo);
}
}
return _Meal;
}
}
private static global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] MealPropInit(global::System.Text.Json.Serialization.JsonSerializerContext context)
{
global::MessageSerializerContext jsonContext = (global::MessageSerializerContext)context;
global::System.Text.Json.JsonSerializerOptions options = context.Options;
global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo[] properties = global::System.Array.Empty<global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo>();
return properties;
}
private static void MealSerializeHandler(global::System.Text.Json.Utf8JsonWriter writer, global::Dark.Model.Meal? value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
writer.WriteEndObject();
}
private static global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] MealCtorParamInit()
{
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[] parameters = new global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues[4];
global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues info;
info = new()
{
Name = "Id",
ParameterType = typeof(global::System.Guid),
Position = 0,
HasDefaultValue = false,
DefaultValue = default(global::System.Guid)
};
parameters[0] = info;
info = new()
{
Name = "RecipeId",
ParameterType = typeof(global::System.Guid),
Position = 1,
HasDefaultValue = false,
DefaultValue = default(global::System.Guid)
};
parameters[1] = info;
info = new()
{
Name = "RecipeTitle",
ParameterType = typeof(global::System.String),
Position = 2,
HasDefaultValue = false,
DefaultValue = default(global::System.String)
};
parameters[2] = info;
info = new()
{
Name = "MealDate",
ParameterType = typeof(global::System.DateTime),
Position = 3,
HasDefaultValue = false,
DefaultValue = default(global::System.DateTime)
};
parameters[3] = info;
return parameters;
}
} |
Reopening for servicing consideration. |
Fixed for 6.0 in #63454. |
This issue has been moved from a ticket on Developer Community.
Hi,
I'm trying to switch from Newtonsoft to System.Text.Json with source generation.
I'm experiencing an issue with serialization of records in another assembly.
I created a simple project, I have a library with a single file containing the record (I don't use terse syntax to easily switch to and from class type)
and an executable with the following code, using System.Text.Json source generation:
The output of the WriteLine is
and then deserialization fails with the following error as it does not find the name:
Switching the type of
Person
fromrecord
toclass
fixes the issue. Also having the record directly in the executable (in the same assembly where the source is generated) fixes the issue.I tried to reproduce by the generated code of the record (IEquatable, ToString, ...) manually in the class, but I can't reproduce the record issue.
By the way, if I look at the generated code in my Visual Studio
If I have a look at the
Person.g.cs
For serialization it seems to contains the right code:
But if I look at the disassembly of my executable (I'm using dotPeek), I see why my serialized object is empty
I attached the project, it's fairly simple.
Thank you
Original Comments
(no comments)
Original Solutions
(no solutions)
The text was updated successfully, but these errors were encountered: