Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a custom JSchemaGenerationProvider that adds a title attribute to both the model and to the model's properties?

I am using the Newtonsoft.Json.Schema package to generate JSON Schemas. Currently, the schemas contain no 'title' property, so I created a custom provider following the example in the documentation , however the provider only runs on the parent node and skips all the property nodes.

class User {
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedDate { get; set; }
}

class TitleProvider : JSchemaGenerationProvider {
    public override JSchema GetSchema(JSchemaTypeGenerationContext context) {
        var schema = new JSchemaGenerator().Generate(context.ObjectType);
        schema.Title = "foo";
        return schema;
    }
}

public class Program {
    public static void Main() {
        var generator = new JSchemaGenerator();
        generator.GenerationProviders.Add(new TitleProvider());
        var schema = generator.Generate(typeof(User));

        Console.WriteLine(schema);
    }
}
// OUTPUT:
//{
//  "title": "foo",
//  "type": "object",
//  "properties": {
//    "Id": {
//      "type": "integer"
//    },
//    "Name": {
//      "type": [
//        "string",
//        "null"
//      ]
//    },
//    "CreatedDate": {
//      "type": "string"
//    }
//  },
//  "required": [
//    "Id",
//    "Name",
//    "CreatedDate"
//  ]
//}

How do I configure this provider to run on property nodes (similar to the example provided in the linked documentation)?

Other Notes:

  • if you return null from the provider GetSchema method, it does iterate over all the properties (which I observed in debugger), though it doesn't have the functionality I want then
  • if I add in an if block to skip when the current context.ObjectType has properties, it does iterate through all the properties, but only adds the title to the first property
like image 382
Nathan Hanna Avatar asked Feb 08 '23 08:02

Nathan Hanna


2 Answers

So I ended up downloading the code and stepping through it and found that once your provider returns a schema it bypasses all of the default processing for all nodes below the current node. So pretty much you have to do all the parsing yourself, or work around the default behavior somehow. I ended up creating a provider that allows you to execute your logic on each node but still gives you the default generated schema to work with:

abstract class RecursiveProvider : JSchemaGenerationProvider {
    public string SkipType { get; set; }
    public override JSchema GetSchema(JSchemaTypeGenerationContext context) {
        var type = context.ObjectType;
        JSchema schema = null;

        var generator = new JSchemaGenerator();

        Console.WriteLine(type.Name);

        var isObject = type.Namespace != "System";

        if (isObject) {
            if (SkipType == type.Name)
                return null;

            this.SkipType = type.Name;
            generator.GenerationProviders.Add(this);
        }

        schema = generator.Generate(type);
        return ModifySchema(schema, context);
    }

    public abstract JSchema ModifySchema(JSchema schema, JSchemaTypeGenerationContext context);

}

class PropertyProvider : RecursiveProvider {
    public override JSchema ModifySchema(JSchema schema, JSchemaTypeGenerationContext context) {
        schema.Title = "My Title";
        return schema;
    }
}
like image 52
Nathan Hanna Avatar answered Feb 09 '23 23:02

Nathan Hanna


The latest version of Json.NET Schema supports DisplayNameAttribute and DescriptionAttribute. Placing those on a type or property will add title and description properties to the generated schema.

Type:

[DisplayName("Postal Address")]
[Description("The mailing address.")]
public class PostalAddress
{
    [DisplayName("Street Address")]
    [Description("The street address. For example, 1600 Amphitheatre Pkwy.")]
    public string StreetAddress { get; set; }

    [DisplayName("Locality")]
    [Description("The locality. For example, Mountain View.")]
    public string AddressLocality { get; set; }

    [DisplayName("Region")]
    [Description("The region. For example, CA.")]
    public string AddressRegion { get; set; }

    [DisplayName("Country")]
    [Description("The country. For example, USA. You can also provide the two letter ISO 3166-1 alpha-2 country code.")]
    public string AddressCountry { get; set; }

    [DisplayName("Postal Code")]
    [Description("The postal code. For example, 94043.")]
    public string PostalCode { get; set; }
}

Code:

JSchemaGenerator generator = new JSchemaGenerator();
generator.DefaultRequired = Required.DisallowNull;

JSchema schema = generator.Generate(typeof(PostalAddress));
// {
//   "title": "Postal Address",
//   "description": "The mailing address.",
//   "type": "object",
//   "properties": {
//     "StreetAddress": {
//       "title": "Street Address",
//       "description": "The street address. For example, 1600 Amphitheatre Pkwy.",
//       "type": "string"
//     },
//     "AddressLocality": {
//       "title": "Locality",
//       "description": "The locality. For example, Mountain View.",
//       "type": "string"
//     },
//     "AddressRegion": {
//       "title": "Region",
//       "description": "The region. For example, CA.",
//       "type": "string"
//     },
//     "AddressCountry": {
//       "title": "Country",
//       "description": "The country. For example, USA. You can also provide the two-letter ISO 3166-1 alpha-2 country code.",
//       "type": "string"
//     },
//     "PostalCode": {
//       "title": "Postal Code",
//       "description": "The postal code. For example, 94043.",
//       "type": "string"
//     }
//   }
// }

https://www.newtonsoft.com/jsonschema/help/html/GenerateWithDescriptions.htm

like image 42
James Newton-King Avatar answered Feb 09 '23 21:02

James Newton-King