2

My Api returns a property which is effectively internal, and I do not expect such a property to be returned.

The situation

I have a simple public class Bar with a public property, and an internal class FooBar which inherits from Bar and adss a public property. However, because the class is internal, that property is effectively also internal, not public.

public class Bar
{
    public string PublicPropertyInPublicBaseClass { get; set; }
}

internal class FooBar : Bar
{
    public string PublicPropertyInInternalClass { get; set; }
}

My controller action specifies it returns a Bar, but the actual instance it returns is of type FooBar.

[HttpGet]
public Bar Get()
{
    return new FooBar();
}

The controller action returns:

{"publicPropertyInInternalClass":null,"publicPropertyInPublicBaseClass":null}

I would expect the Json serializer to only expose the properties of specified type (Bar), so I would not expect publicPropertyInInternalClass. And even if it is expected to expose the properties of the actual type, I would expect it to never expose a property of an internal type.

What I've tried

If I understand correctly, .NET Core 3.1 and higher uses System.Text.Json in stead of the older NewtonSoft.Json. I wrote a simple test to see what System.Text.Json would do and what NewtonSoft.Json does.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNetCoreSerializer()
    {
        Bar sut = CreateBar();

        var serialized = System.Text.Json.JsonSerializer.Serialize(sut);

        Debug.WriteLine(serialized); 
        // {"PublicPropertyInPublicBaseClass":null}
    }

    [TestMethod]
    public void TestNewtonSoftSerializer()
    {
        Bar sut = CreateBar();

        var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(sut);

        Debug.WriteLine(serialized); 
        // {"PublicPropertyInInternalClass":null,"PublicPropertyInPublicBaseClass":null}
    }

    private static Bar CreateBar()
    {
        // We actually return a FooBar, which inherits from Bar
        return new FooBar();
    }
}

So now I am confused. The System.Text.Json serializer only exposes the property that is realy public, and I'd expect the controller to use that serializer. But what the controller returns seems to be the NewtonSoft.Json behavior.

What is going on here? Is the behavior I'm seeing expected? Why? Why would a serializer serialize the actual type? And why is this different between NewtonSoft.Json and System.Text.Json?

And maybe the most important question: am I doing something wrong by having an internal class inherit from a public class while that public class is part of the output model of my API? Or by having a public property in an internal class?

I've tried this also with both .NET Core 3.1 and .NET 5.0.

comecme
  • 6,086
  • 10
  • 39
  • 67

1 Answers1

0

What is going on here? Is the behavior I'm seeing expected? Why? Why would a serializer serialize the actual type? And why is this different between NewtonSoft.Json and System.Text.Json?

Firstly I need to say,by using your code in my project,it gets the same result ({"PublicPropertyInInternalClass":null,"PublicPropertyInPublicBaseClass":null}).

And you may misunderstand the usage of internal access modifier.If the property decalred with internal,it means it is accessible only within files in the same assembly.

One scenario:

If you use the two assemblies,you need add the internal access modifier to the property instead of the class,then you could create the instance in the other assembly but could not access the internal member:

Assembly1:

public class Bar
{
    public string PublicPropertyInPublicBaseClass { get; set; }
}

public class FooBar : Bar
{
    internal string PublicPropertyInInternalClass { get; set; }
}

Assembly2:

[HttpGet]
public string Get()
{
    var data = new Assembly1.Models.FooBar();
    var serialized = System.Text.Json.JsonSerializer.Serialize(data);
    var serialized2 = Newtonsoft.Json.JsonConvert.SerializeObject(data);
    return serialized;
}

Note: If you use the Newtonsoft.Json in asp.net core 3.x or asp.net 5,remember to add Newtonsoft support below,more details refer to the answer here.Otherwise,you will still get this result({"PublicPropertyInInternalClass":null,"PublicPropertyInPublicBaseClass":null}).

services.AddControllersWithViews()
    .AddNewtonsoftJson();

Another scenario:

If you use the model in the same assembly,you could use [JsonIgnore] property:

public class Bar
{
    public string PublicPropertyInPublicBaseClass { get; set; }
}

internal class FooBar : Bar
{
    [JsonIgnore]
    public string PublicPropertyInInternalClass { get; set; }
}

Note:Both NewtonSoft.Json and System.Text.Json contain JsonIgnore attribute.Be sure add the correct package reference:

Rena
  • 30,832
  • 6
  • 37
  • 72