JsonConvert.DeserializeObjects 无法与 trim unused code 发布设置一起使用。

huangapple go评论58阅读模式
英文:

JsonConvert.DeserializeObjects does not work with trim unused code publish setting

问题

The method JsonConvert.DeserializeObjects 在“修剪未使用的代码”发布设置关闭时工作。当我打开此设置时,出现以下错误:

> Newtonsoft.Json.JsonSerializationException:找不到用于类型MyNamespace.MyObject的构造函数。一个类应该有一个默认构造函数,一个带参数的构造函数或带有JsonConstructor属性的构造函数。路径“namespace”,行1,位置13。

这是一个简单的对象,没有构造函数。我已尝试添加一个无参数的空构造函数,但没有改变。

发布设置

  • 配置:Release | Any CPU
  • 目标框架:net7.0-windows10.0.22621.0 -- 使用net7.0而不是特定于操作系统的目标也会导致此问题
  • 部署模式:Self - contained
  • 目标运行时:win - x64
  • 生成单个文件:开启
  • 启用ReadyToRun编译:开启
  • 修剪未使用的代码:开启 - 关闭此设置会使文件大小翻倍,但问题会消失

这与https://stackoverflow.com/questions/37110945/jsonconvert-deserializeobjects-does-not-work-after-linking-sdk-and-user-assembli中的一个类似的问题,但适用于Windows。那个问题中的解决方法对我不起作用。

有人有这个问题的解决方法吗?

更新:

以下是一个最小的示例:

using Newtonsoft.Json;

namespace ConsoleApp1;

internal class Program
{
	static void Main( string[] args )
	{
		var res = JsonConvert.DeserializeObject<Response>( "{\"blah\": [ { \"name\": \"test\", \"id\": \"test\" }, { \"name\": \"test2\", \"id\": \"test2\" } ] }" );
	}
}

public class Blah
{
	[JsonProperty( "name", NullValueHandling = NullValueHandling.Ignore )]
	public string Name { get; set; } = "";

	[JsonProperty( "id", NullValueHandling = NullValueHandling.Ignore )]
	public string Id { get; set; } = "";
}

public class Response
{
	[JsonProperty( "blah", NullValueHandling = NullValueHandling.Ignore )]
	public List<Blah> SomeList { get; } = new List<Blah>();
}

以上代码在正常编译时运行正常(无论是Debug还是Release)。在使用上述提到的设置发布时,它会抛出错误:

> 未处理的异常。Newtonsoft.Json.JsonSerializationException:
> 找不到用于类型ConsoleApp1.Response的构造函数。一个类应该有一个默认构造函数,一个带参数的构造函数或带有JsonConstructor属性的构造函数。
> 路径‘blah’,行1,位置9。 在
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader,
> JsonObjectContract,JsonProperty,JsonProperty,String,Boolean&)


> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader
> ,Type,JsonContract,JsonProperty,JsonContainerContract,
> JsonProperty,Object) 在
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader
> ,Type,JsonContract,JsonProperty,JsonContainerContract,
> JsonProperty,Object) 在
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader
> ,Type,Boolean) 在
> Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader,Type)
在Newtonsoft.Json.JsonConvert.DeserializeObject(String,Type,
> JsonSerializerSettings) 在
> Newtonsoft.Json.JsonConvert.DeserializeObject[T](String,
> JsonSerializerSettings) 在ConsoleApp1.Program.Main(String[])中
在C:\Users\username\source\repos\MREJsonIssue\ConsoleApp1\Program.cs:line
> 9

更新2:

针对.NET 6.0不会出现此问题。

英文:

The method JsonConvert.DeserializeObjects works when "Trim unused code" publish setting is off. When I turn this setting on I get:

> Newtonsoft.Json.JsonSerializationException: Unable to find a
> constructor to use for type MyNamespace.MyObject. A class should
> either have a default constructor, one constructor with arguments or a
> constructor marked with the JsonConstructor attribute. Path
> 'namespace', line 1, position 13.

It is a simple object, with no constructors. I have tried adding a blank constructor without arguments, nothing has changed.

Publish settings

  • Configuration: Release | Any CPU
  • Target Framework: net7.0-windows10.0.22621.0 -- using net7.0 without OS specific targeting also causes this problem
  • Deployment mode: Self - contained
  • Target runtime: win - x64
  • Produce single file: ON
  • Enable ReadyToRun compilation: ON
  • Trim unused code: ON - turning off this setting doubles the file size but the issue goes away

This is a similar question to https://stackoverflow.com/questions/37110945/jsonconvert-deserializeobjects-does-not-work-after-linking-sdk-and-user-assembli but for Windows. The solution in that question does not work for me.

Does anyone have any workarounds for this problem?

Updated:

Bellow is a minimal example:

using Newtonsoft.Json;

namespace ConsoleApp1;

internal class Program
{
	static void Main( string[] args )
	{
		var res = JsonConvert.DeserializeObject&lt;Response&gt;( &quot;{ \&quot;blah\&quot;: [ { \&quot;name\&quot;: \&quot;test\&quot;, \&quot;id\&quot;: \&quot;test\&quot; }, { \&quot;name\&quot;: \&quot;test2\&quot;, \&quot;id\&quot;: \&quot;test2\&quot; } ] }&quot; );
	}
}

public class Blah
{
	[JsonProperty( &quot;name&quot;, NullValueHandling = NullValueHandling.Ignore )]
	public string Name { get; set; } = &quot;&quot;;

	[JsonProperty( &quot;id&quot;, NullValueHandling = NullValueHandling.Ignore )]
	public string Id { get; set; } = &quot;&quot;;
}

public class Response
{
	[JsonProperty( &quot;blah&quot;, NullValueHandling = NullValueHandling.Ignore )]
	public List&lt;Blah&gt; SomeList { get; } = new List&lt;Blah&gt;();
}

Above code runs fine when compiled normally (either Debug or Release). When published with above mentioned settings it throws an error:

> Unhandled exception. Newtonsoft.Json.JsonSerializationException:
> Unable to find a constructor to use for type ConsoleApp1.Response. A
> class should either have a default constructor, one constructor with
> arguments or a constructor marked with the JsonConstructor attribute.
> Path 'blah', line 1, position 9. at
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader,
> JsonObjectContract, JsonProperty , JsonProperty , String , Boolean& )
> at
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader
> , Type, JsonContract, JsonProperty, JsonContainerContract,
> JsonProperty, Object) at
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader
> , Type, JsonContract, JsonProperty, JsonContainerContract,
> JsonProperty, Object) at
> Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader
> , Type, Boolean) at
> Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader , Type)
> at Newtonsoft.Json.JsonConvert.DeserializeObject(String , Type,
> JsonSerializerSettings) at
> Newtonsoft.Json.JsonConvert.DeserializeObject[T](String ,
> JsonSerializerSettings) at ConsoleApp1.Program.Main(String[]) in
> C:\Users\username\source\repos\MREJsonIssue\ConsoleApp1\Program.cs:line
> 9

Update 2:

Targeting .NET 6.0 does not produce this issue.

答案1

得分: 1

I have found two potential workarounds:

  1. Mark the main method with explicit DynamicDependency attribute:

    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Response))]
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Blah))]
    static void Main( string[] args )
    
  2. Moving classes to a separate assembly and marking it with <IsTrimmable>true</IsTrimmable> and <TrimMode>copyused</TrimMode>

  3. Specifying <TrimMode>partial</TrimMode> and excluding assembly from trimming by or rooting it (not sure what the difference here):

    <ItemGroup>
       <TrimmerRootAssembly Include="ConsoleApp1" />
       <!-- or -->
       <!-- <TrimmableAssembly Remove="ConsoleApp1" /> -->
    </ItemGroup>
    

P.S. there was another workaround (😁) - using reflection to get both types constructors explicitly in the program (Console.WriteLine(typeof(Response).GetConstructors().Length); and Console.WriteLine(typeof(Blah).GetConstructors().Length);)

Also check out the docs.

英文:

I have found two potential workarounds:

  1. Mark the main method with explicit DynamicDependency attribute:

    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Response))]
    [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Blah))]
    static void Main( string[] args )
    
  2. Moving classes to a separate assembly and marking it with &lt;IsTrimmable&gt;true&lt;/IsTrimmable&gt; and &lt;TrimMode&gt;copyused&lt;/TrimMode&gt;

  3. Specifying &lt;TrimMode&gt;partial&lt;/TrimMode&gt; and excluding assembly from trimming by or rooting it (not sure what the difference here):

    &lt;ItemGroup&gt;
       &lt;TrimmerRootAssembly Include=&quot;ConsoleApp1&quot; /&gt;
       &lt;!-- or --&gt;
       &lt;!--&lt;TrimmableAssembly Remove=&quot;ConsoleApp1&quot; /&gt;--&gt;
    &lt;/ItemGroup&gt;
    

P.S. there was another workaround (😁) - using reflection to get both types constructors explicitly in the program (Console.WriteLine(typeof(Response).GetConstructors().Length); and Console.WriteLine(typeof(Blah).GetConstructors().Length);)

Also check out the docs.

答案2

得分: 0

以下是翻译好的部分:

很难重现您的问题,但您的属性没有任何setter。添加一个(例如init)。在我看来,手动初始化类中的每个属性从来都不是一个好主意。您应该始终假定此属性为空。这样,您将避免空引用异常。
public class Response
{
    [JsonProperty("blah", NullValueHandling = NullValueHandling.Ignore)]
    public List<Blah> SomeList { get; init; } // = new List<Blah>();
}
英文:

it is hard to reproduce your problem, but your property doesn't have any setter. Add the one (init for example). And IMHO it is never good idea to init manually every property in the class. You should always assume that this property is null. This way you will avoid null reference exceptions.

public class Response
{
    [JsonProperty( &quot;blah&quot;, NullValueHandling = NullValueHandling.Ignore )]
	public List&lt;Blah&gt; SomeList { get; init; } // = new List&lt;Blah&gt;();
}

</details>



huangapple
  • 本文由 发表于 2023年3月8日 19:01:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75672166.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定