Skip to content
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

JSONProperty not working correctly in immutable models #2057

Closed
paulhickman-a365 opened this issue Mar 12, 2021 · 4 comments
Closed

JSONProperty not working correctly in immutable models #2057

paulhickman-a365 opened this issue Mar 12, 2021 · 4 comments
Labels
responded Responded with solution or request for more info

Comments

@paulhickman-a365
Copy link

When using NewtonsSoft serialization, ASP.Net Core supports model binding using immutable models in controller methods. To work, this requires that the model has a constructor which has an argument for each JSON property with the same name as the property name (allowing a camel case vs. title case difference). This is also compatible with using [JsonProperty] to rename a property - the constructor has an argument with the real property name but it binds to the name specified in the [JsonProperty].

In the following controller, the two methods Mutable and Immutable both have the same JSON signature:

    [ApiController]
    [Route("Test")]
    public class TestController : ControllerBase
    {
        public class MutableModel
        {
            public int Prop1 { get; set; }

            [JsonProperty("prop2")]
            public int PropTwo { get; set; }
        }

        [HttpPost]
        [Route("Mutable")]
        public IActionResult Mutable([FromBody] MutableModel request)
        {
            return request.Prop1 == 1 && request.PropTwo == 1 ? Ok() : StatusCode(400);
        }

        public class ImmutableModel
        {
            public ImmutableModel(int prop1, int propTwo)
            {
                Prop1 = prop1;
                PropTwo = propTwo;
            }

            public int Prop1 { get; }

            [JsonProperty("prop2")]
            public int PropTwo { get; set; }
        }

        [HttpPost]
        [Route("Immutable")]
        public IActionResult Immutable([FromBody] ImmutableModel request)
        {
            return request.Prop1 == 1 && request.PropTwo == 1 ? Ok() : StatusCode(400);
        }
    }

With NewtsonSoft serialization, both of these methods will accept the JSON POST body of:

{
   "prop1": 1,
   "prop2": 1
}

However, the swagger.json generated is:

{
  "openapi": "3.0.1",
  "info": {
    "title": "My API",
    "version": "v1"
  },
  "paths": {
    "/Test/Mutable": {
      "post": {
        "tags": [
          "Test"
        ],
        "requestBody": {
          "content": {
            "application/json-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/MutableModel"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MutableModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/MutableModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/MutableModel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    },
    "/Test/Immutable": {
      "post": {
        "tags": [
          "Test"
        ],
        "requestBody": {
          "content": {
            "application/json-patch+json": {
              "schema": {
                "$ref": "#/components/schemas/ImmutableModel"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImmutableModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/ImmutableModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/ImmutableModel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ImmutableModel": {
        "type": "object",
        "properties": {
          "prop1": {
            "type": "integer",
            "format": "int32"
          },
          "prop2": {
            "type": "integer",
            "format": "int32",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "MutableModel": {
        "type": "object",
        "properties": {
          "prop1": {
            "type": "integer",
            "format": "int32"
          },
          "prop2": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      }
    }
  }
}

Swashbuckle isn't allowing for the [JsonProperty] binding logic and is incorrectly marking prop2 as read-only.

Example project: SwashTest.zip

@domaindrivendev
Copy link
Owner

domaindrivendev commented Mar 12, 2021

Have you configured Swashbuckle to honor Newtonsoft behavior instead of STJ (the default) as described in the readme?
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#systemtextjson-stj-vs-newtonsoft

Also, your example link is broken

@paulhickman-a365
Copy link
Author

paulhickman-a365 commented Mar 15, 2021

Yes, Swashbuckle is configured correctly - you can see that prop2 rather than propTwo appears in the swagger JSON which requires that it is setup to use NewtonSoft, and understand [JsonProperty]

The problem is that the Swashbuckle anlayzer for NewtonSoft doesn't allow for using JsonProperty to rename a property when matching that property name to the constructor argument name to determine if it is settable.

I'll try attaching the project again here:
SwashTest.zip

@domaindrivendev
Copy link
Owner

domaindrivendev commented Mar 16, 2021

OK dicovered the issue and fixed with a6eda1a. Should now be available as of 6.1.1. Can you pull down the latest version and see if it resolves your issue

@domaindrivendev domaindrivendev added the responded Responded with solution or request for more info label Mar 16, 2021
This was referenced Mar 17, 2021
This was referenced Mar 17, 2021
@paulhickman-a365
Copy link
Author

This has resolved the issue - thanks for the prompt update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
responded Responded with solution or request for more info
Projects
None yet
Development

No branches or pull requests

2 participants