Search code examples
c#json.netjsonschema

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

Solution

  • 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;
        }
    }