-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[API Proposal]: Add a JSON schema exporting component for STJ contracts #102788
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis |
As a maintainer of the JSON Schema specification, I'm very interested in the mapping between types and their constituents and the schemas that are generated. I consider this mapping part of the API. I would like to suggest very limited functionality (just obvious stuff, like what you show in your example) to start. I recommend steering away from defining edge cases. We have a project in JSON Schema that seeks to define that mapping in a language-agnostic way. While there are plenty of generators (codegen / schemagen), so far none of these have jumped in to help define these rules, instead merely adding to the pool of non-interoperability by doing what makes sense to them. I'd love for this effort to contribute back into our project to help define the mapping between JSON Schema and types. What I'd like to see out of this effort is a beginning toward generating a schema from a type, then being able to take that schema and generate equivalent types in other languages. As a .Net developer, I understand that this isn't the final vision for JSON Schema provided by .Net. As such, I wonder where this fits in the future when the final vision (whatever that ends up being) is realized. Will these types and methods still be available? Are we comfortable with this existing alongside some potential dedicated JSON Schema model? |
Specific notes about API:
|
To clarify, this is not a .NET type to JSON schema mapping component, it is an STJ contract (aka
I'm a firm believer of schema-first approaches, namely using schemas to generate types and not the other way around. Code-first largely relies on convention and can be imprecise because not all type system concepts have first-class representation or are relevant in a particular schematization format. I realize I'm stating this in an API proposal that caters to code-first scenaria (still a huge deal in .NET!), however longer-term I would personally place my bets on schema-first.
Longer term, we need an exchange type that offers basic validation and keyword extensibility. I've purposely designed the proposal so that corresponding APIs targeting the exchange type can be added in the future by qualifying method names with "Node", e.g. "GetJsonSchemaAsNode" and "OnSchemaNodeGenerated". |
That's changing in .NET 9 (for properties at least). We stopped short of making it the default behaviour for fear of breaking changes, but given that this is a new component we have the luxury of being more deliberate about the desirable behaviour.
Similar to the serializer, it will throw an exception if the
It provides context when visiting the schema for |
This is a nuance. I think most people are going to see/use it as generating a schema from a type; it just has to go through the STJ source generator a little, which kinda makes sense because it's a JSON schema. I doubt (public) users will really see the difference. Regardless, there's still some underlying logic that dictates what kinds of .Net constructions yield what kinds of schema constructions. That's what the IDL vocab project (basically me) is interested in.
I'm excited to see this take hold across .Net! (cc: @markrendle / @mwadams)
Yeah, I'm just curious if these are expected to be utilized still when the future version is implemented. If not, is this considered acceptable bloat? I'm not sure how this kind of thing is handled in .Net.
You're changing the default to serialize nulls? That seems inefficient. Do you have an issue/PR for that? |
We can't ever remove APIs once they have been shipped, so it's expected that they would live on next to the future APIs that target an exchange type. It shouldn't be a huge issue size-wise, by that point they just be stub methods that convert to
It's adding non-nullable reference type enforcement. See #100144 |
I think I'm interpreting things a bit wrong. The default for the serializer is to skip null properties when writing, and the default for this is to generate a schema that allows null properties. My thinking was that these are related, but really the schema generation is related to serializer reads where the default behavior is to allow nulls, which does align with the default schema generation. Disallowing nulls in the schema aligns with the non-default behavior of disallowing nulls in deserialization, per that issue. I think we're good here. |
That's not the default behavior, unless
That's an interesting observation. The prototype implementation is actually generating a schema that satisfies the union of serialization outputs and valid deserialization inputs. In other words, the schema of a property allows null if either the getter or the setter are nullable (it's possible for the two to differ in annotation). |
namespace System.Text.Json.Schema; // New namespace
public static class JsonSchemaExporter
{
public static JsonObject GetJsonSchemaAsNode(JsonSerializerOptions options, Type type, JsonSchemaExporterOptions? exporterOptions = null);
public static JsonObject GetJsonSchemaAsNode(JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOptions = null);
}
public sealed class JsonSchemaExporterOptions
{
/// The default options singleton.
public static JsonSchemaExporterOptions Default { get; } = new();
/// Determines whether schema references should be generated for recurring schema nodes.
public bool AllowSchemaReferences { get; init; } = true;
/// Defines a callback that is invoked for every schema that is generated within the type graph.
public Action<JsonSchemaExporterContext, JsonObject>? OnSchemaNodeGenerated { get; init; }
}
/// Context of the current schema node being generated.
public readonly struct JsonSchemaExporterContext
{
/// The parent collection type if the schema is being generated for a collection element or dictionary value.
public ReadOnlySpan<string> Path { get; }
/// The JsonTypeInfo for the type being processed.
public JsonTypeInfo TypeInfo { get; }
/// The JsonPropertyInfo if the schema is being generated for a property.
public JsonPropertyInfo? PropertyInfo { get; }
} |
I think the API looks good. Just reiterating that I'm still interested in the eventual logic behind it. What code constructs produce what schema constructs? To be answered later, I assume, but it would be good to have this publicly documented, and I'd like to help guide these decisions. |
Essentially it's just going to be a port of https://github.com/eiriktsarpalis/stj-schema-mapper, the unit tests should provide an idea of how it's meant to work. |
I've submitted eiriktsarpalis/stj-schema-mapper#2 for some recommendations. |
I would like to propose the following amendments to the API as approved:
namespace System.Text.Json.Schema;
public static class JsonSchemaExporter
{
- public static JsonObject GetJsonSchemaAsNode(JsonSerializerOptions options, Type type, JsonSchemaExporterOptions? exporterOptions = null);
- public static JsonObject GetJsonSchemaAsNode(JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOptions = null);
+ public static JsonNode GetJsonSchemaAsNode(JsonSerializerOptions options, Type type, JsonSchemaExporterOptions? exporterOptions = null);
+ public static JsonNode GetJsonSchemaAsNode(JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOptions = null);
}
public sealed class JsonSchemaExporterOptions
{
- public Action<JsonSchemaExporterContext, JsonObject>? OnSchemaNodeGenerated { get; init; }
+ public Func<JsonSchemaExporterContext, JsonNode, JsonNode>? TransformGeneratedSchemaNode { get; init; }
+ public bool TreatNullObliviousAsNullable { get; init; } = true;
} |
Introduction
This issue defines the JSON schema exporting APIs largely following the design of the stj-schema-mapper prototype. Rather than introducing a JSON schema exchange type, the proposed exporter methods generate schema documents represented as
JsonNode
instances which can be modified or mapped to other schema models as required.The exporter employs a callback model allowing users to enrich the generated schema for every node in the generated type graph using metadata from arbitrary attribute annotations.
Contributes to #100159
API Proposal
API Usage
Here's an example using the callback API to extract schema information from other attributes:
cc @captainsafia @stephentoub @gregsdennis
The text was updated successfully, but these errors were encountered: