System.Text.Json Field Serialization in .NET 5 not shown in Swashbuckle API Definition
It seems like Swashbuckle doesn't use the JsonSerializerOptions
to generate the docs. One workaround i found is to handle the types manually:
public class FieldsSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var fields = context.Type.GetFields();
if (fields == null) return;
if (fields.Length == 0) return;
foreach (var field in fields)
{
schema.Properties[field.Name] = new OpenApiSchema
{
// this should be mapped to an OpenApiSchema type
Type = field.FieldType.Name
};
}
}
}
Then in your Startup.cs ConfigureServices:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
c.SchemaFilter<FieldsSchemaFilter>();
});
When stepping through, you'll see the JsonSerializerOptions
used in the SchemaFilterContext
(SchemaGenerator
). IncludeFields
is set to true. Still only properties are used for docs, so I guess a filter like that is your best bet.
The issue has no thing to do with Swagger, it is pure serialization issue.
You have 3 solutions:
- Write your own customized json for vector. (just concept)
- Use a customized object with primitive types and map it. (just concept)
- Use Newtonsoft.Json (suggested solution)
Regarding to Microsoft doc, System.Text.Json
you can see in the comparing list, that System.Text.Json might have some limitation.
If you want the suggested solution jump directly to solution 3.
Let's take the first concept of custom serialized. Btw this custom example is just for demonstration and not full solution.
So what you can do is following:
- Create a custom vector
CustomVector
model. - Create a custom
VectorConverter
class that extendJsonConverter
. - Added some mapping.
- Put the attribute
VectorConverter
to vector property.
Here is my attempt CustomVector:
public class CustomVector
{
public float? X { get; set; }
public float? Y { get; set; }
public float? Z { get; set; }
}
And custom VectorConverter:
public class VectorConverter : JsonConverter<Vector3>
{
public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// just for now
return new Vector3();
}
public override void Write(Utf8JsonWriter writer, Vector3 data, JsonSerializerOptions options)
{
// just for now
var customVector = new CustomVector
{
X = data.X,
Y = data.Y,
Z = data.Z
};
var result = JsonSerializer.Serialize(customVector);
writer.WriteStringValue(result);
}
}
And you vector property, added the following attribute:
[JsonConverter(typeof(VectorConverter))]
public Vector3 Vector { get; set; }
This will return following result:
Now this solve part of the issue, if you want to post a vector object, you will have another challenge, that also depends on your implementation logic.
Therefore, comes my second solution attempt where we expose our custom vector and ignore vector3 in json and map it to/from Vector3 from our code:
So hence we have introduces a CustomVector
, we can use that in stead of Vector3 in our model, than map it to our Vector3.
public class Test
{
public string Field { get; set; }
public string Property { get; set; }
[JsonIgnore]
public Vector3 Vector { get; set; }
public CustomVector CustomVector { get; set; }
}
Here is a get and post method with mapping example:
[HttpGet]
public Test Get()
{
var vector = new CustomVector() { X = 1, Y = 1, Z = 1 };
var test = new Test
{
Field = "Field",
Property = "Property",
CustomVector = vector
};
VectorMapping(test);
return test;
}
[HttpPost]
public Test Post(Test test)
{
VectorMapping(test);
return test;
}
private static void VectorMapping(Test test)
{
test.Vector = new Vector3
{
X = test.CustomVector.X.GetValueOrDefault(),
Y = test.CustomVector.Y.GetValueOrDefault(),
Z = test.CustomVector.Z.GetValueOrDefault()
};
}
The down side in first solution, we need to write a full customize serializing, and in our second solution we have introduced extra model and mapping.
The suggested solution
Therefore I suggest the following and 3rd attempt:
Keep every thing you have as it is in your solution, just added nuget Swashbuckle.AspNetCore.Newtonsoft
to your project, like:
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />
And in your startup
services.AddSwaggerGenNewtonsoftSupport();
Fire up, and this will generate the documentation, as it allow serializing and deserializing Vector3 and other class types that are not supported by System.Text.Json
.
As you can see this include now Vector3 in documentation:
I am pretty sure this can be done other ways. So this is my attempts solving it.