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

747 Views Asked by At

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 JsonConvert.DeserializeObjects does not work after Linking SDK and User Assemblies 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<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>();
}

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.

3

There are 3 best solutions below

2
Guru Stron On BEST ANSWER

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.

3
Serge On

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( "blah", NullValueHandling = NullValueHandling.Ignore )]
    public List<Blah> SomeList { get; init; } // = new List<Blah>();
}
0
JoLab On

Had a similar problem using .net 8.0

Console app using using:

  1. Newtonsoft.Json (specifically JsonConvert.DeserializeObject)
  2. RestSharp
  3. CommandLine
  4. Colorful.Console

All producing trim warnings on build

Had some other runtime exceptions from above packages as well before hitting the JsonConvert issue, like :

System.InvalidOperationException: Type foo appears to be immutable, but no constructor found to accept values.

Only had to add <TrimMode>partial</TrimMode> to PropertyGroup in .csproj

Example:

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <TrimMode>partial</TrimMode>
</PropertyGroup>

Issue resolved (tested) for me on following RIDs

  • win-x64
  • linux-x64