Skip to content

Allow to specify type to serialize in SystemTextJsonOutputFormatter  #41399

Open
@ArturDorochowicz

Description

@ArturDorochowicz

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I'm trying to specify what type the SystemTextJsonOutputFormatter should serialize, but it's not possible, because it goes out of its way to use the actual type of the value.

Let's consider code like this:

class Parent {}
class Child : Parent {
  public string Prop { get; set; }
}

[ApiController]
class Ctrl : ControllerBase {
  [HttpGet]
  public Parent Get1() {
    return new Child();
  }

  [HttpGet]
  public ActionResult<Parent> Get2()
  {
    return new Child();
  }

  [HttpGet]
  public IActionResult Get3() {
    // getting desperate ...
    var ok = Ok(new Child());
    ok.DeclaredType = typeof(Parent);
    return ok;
  }
}

In all cases above, the value is serialized as Child and not Parent. In fact there seems to be no way to serialize it as Parent.

This is because of code in SystemTextJsonOutputFormatter

// context.ObjectType reflects the declared model type when specified.
// For polymorphic scenarios where the user declares a return type, but returns a derived type,
// we want to serialize all the properties on the derived type. This keeps parity with
// the behavior you get when the user does not declare the return type and with Json.Net at least at the top level.
var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object);

I understand the sentiment. As the comment says, you want to be compatible with NewtonsoftJson at least at the root level. But I think it goes too far and hope that there can be a way added to specify the type for serialization.

I think the comment: "context.ObjectType reflects the declared model type when specified" is inaccurate. In case of simple return type (Parent Get() {}), the ObjectType property is in fact set to the runtime type of the instance due to code in OutputResultExecutor:

var objectType = result.DeclaredType;
if (objectType == null || objectType == typeof(object))
{
objectType = result.Value?.GetType();
}

Describe the solution you'd like

I understand that there's slim chance you'll change the existing behavior, because it will be a breaking change. But anyway, I'd like the cases where ObjectResult specifies the DeclaredType to pass that type to the STJ serializer. That means that in the example code above, second and third cases should serialize the value as Parent and not Child.

Additional context

I stumbled upon this problem when analyzing some code that directly returns EF entities out of MVC actions. The entity used virtual property for lazy loading support. The property was decorated with JsonIgnore attribute, but it didn't work.

The analysis showed that Mvc passes the EF-generated proxy type to STJ and STJ doesn't read inherited attributes (GetCustomAttributes(..., inherit: false). The end result is that lazy loaded property was getting serialized while the intention was to ignore it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templates

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions