Json.Net.Schema:如何生成仅需要 [Required] 属性的架构?

Json.Net.Schema: How to generate a schema where only the [Required] properties are required?

我正在编写一个工具来处理 resume.json 文件 (project),并且正在创建一个 Json 架构来验证用户输入。我正在尝试使用 Json.Net.Schema 自动执行此操作,但输出始终需要所有属性,无论属性是否具有 [Required][JsonRequired]属性。

模式生成代码

var generator = new JSchemaGenerator
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName
};

var schema = generator.Generate(typeof(Resume));
var sb = new StringBuilder();
schema.WriteTo(new JsonTextWriter(
    new IndentedTextWriter(
        new StringWriter(sb),
        "  ")
    )
{
    Formatting = Formatting.Indented,
    IndentChar = ' ', Indentation = 2,
    QuoteName = true
}, new JSchemaWriterSettings
{
    Version = SchemaVersion.Draft7
});

File.WriteAllText("E:\resume.schema", sb.ToString());

恢复 Class(和 children)

public class Resume
{
    [Required]
    public Basics Basics { get; set; }
    public Work[] Work { get; set; }
    public Volunteer[] Volunteer { get; set; }
    public Education[] Education { get; set; }
    public Award[] Awards { get; set; }
    public Publication[] Publications { get; set; }
    public Skill[] Skills { get; set; }
    public Language[] Languages { get; set; }
    public Interest[] Interests { get; set; }
    public Reference[] References { get; set; }
}

public class Award
{
    [Required]
    public string Title { get; set; }
    public string Date { get; set; }
    public string Awarder { get; set; }
    public string Summary { get; set; }
}

public class Basics
{
    [Required]
    public string Name { get; set; }
    public string Label { get; set; }
    public Uri Picture { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Website { get; set; }
    public string Summary { get; set; }
    public Location Location { get; set; }
    public Profile[] Profiles { get; set; }
}

public class Education
{
    [Required]
    public string Institution { get; set; }
    public string Area { get; set; }
    public string StudyType { get; set; }
    public string StartDate { get; set; }
    public string EndDate { get; set; }
    public string Gpa { get; set; }
    public string[] Courses { get; set; }
}

public class Interest
{
    [Required]
    public string Name { get; set; }
    public string[] Keywords { get; set; }
}

public class Language
{
    [Required]
    public string language { get; set; }
    [Required]
    public string Fluency { get; set; }
}

public class Location
{
    public string Address { get; set; }
    [Required]
    public string PostalCode { get; set; }
    [Required]
    public string City { get; set; }
    [Required]
    public string CountryCode { get; set; }
    public string Region { get; set; }
}

public class Profile
{
    [Required]
    public string Network { get; set; }
    [Required]
    public string Username { get; set; }
    [Required]
    public string Url { get; set; }
}

public class Publication
{
    [Required]
    public string Name { get; set; }
    public string Publisher { get; set; }
    public string ReleaseDate { get; set; }
    public string Website { get; set; }
    public string Summary { get; set; }
}

public class Reference
{
    [Required]
    public string Name { get; set; }
    public string reference { get; set; }
}

public class Skill
{
    [Required]
    public string Name { get; set; }
    [Required]
    public string Level { get; set; }
    public string[] Keywords { get; set; }
}

public class Volunteer
{
    [Required]
    public string Organization { get; set; }
    [Required]
    public string Position { get; set; }
    public string Website { get; set; }
    public string StartDate { get; set; }
    public string EndDate { get; set; }
    public string Summary { get; set; }
    public string[] Highlights { get; set; }
}

public class Work
{
    [Required]
    public string Company { get; set; }
    [Required]
    public string Position { get; set; }
    public string Website { get; set; }
    [Required]
    public string StartDate { get; set; }
    public string EndDate { get; set; }
    public string Summary { get; set; }
    public string[] Highlights { get; set; }
}

当前输出

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "Resume",
  "definitions": {
    "Award": {
      "$id": "Award",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "title": {
          "type": "string"
        },
        "date": {
          "type": [
            "string",
            "null"
          ]
        },
        "awarder": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "title",
        "date",
        "awarder",
        "summary"
      ]
    },
    "Basics": {
      "$id": "Basics",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "label": {
          "type": [
            "string",
            "null"
          ]
        },
        "picture": {
          "type": [
            "string",
            "null"
          ],
          "format": "uri"
        },
        "email": {
          "type": [
            "string",
            "null"
          ]
        },
        "phone": {
          "type": [
            "string",
            "null"
          ]
        },
        "website": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        },
        "location": {
          "$id": "Location",
          "type": [
            "object",
            "null"
          ],
          "properties": {
            "address": {
              "type": [
                "string",
                "null"
              ]
            },
            "postalCode": {
              "type": "string"
            },
            "city": {
              "type": "string"
            },
            "countryCode": {
              "type": "string"
            },
            "region": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "address",
            "postalCode",
            "city",
            "countryCode",
            "region"
          ]
        },
        "profiles": {
          "$id": "Profile[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "$id": "Profile",
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "network": {
                "type": "string"
              },
              "username": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "network",
              "username",
              "url"
            ]
          }
        }
      },
      "required": [
        "name",
        "label",
        "picture",
        "email",
        "phone",
        "website",
        "summary",
        "location",
        "profiles"
      ]
    },
    "Education": {
      "$id": "Education",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "institution": {
          "type": "string"
        },
        "area": {
          "type": [
            "string",
            "null"
          ]
        },
        "studyType": {
          "type": [
            "string",
            "null"
          ]
        },
        "startDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "endDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "gpa": {
          "type": [
            "string",
            "null"
          ]
        },
        "courses": {
          "$id": "String[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "required": [
        "institution",
        "area",
        "studyType",
        "startDate",
        "endDate",
        "gpa",
        "courses"
      ]
    },
    "Interest": {
      "$id": "Interest",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "keywords": {
          "$id": "String[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "required": [
        "name",
        "keywords"
      ]
    },
    "Language": {
      "$id": "Language",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "language": {
          "type": "string"
        },
        "fluency": {
          "type": "string"
        }
      },
      "required": [
        "language",
        "fluency"
      ]
    },
    "Location": {
      "$ref": "Location"
    },
    "Profile": {
      "$ref": "Profile"
    },
    "Publication": {
      "$id": "Publication",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "publisher": {
          "type": [
            "string",
            "null"
          ]
        },
        "releaseDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "website": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "name",
        "publisher",
        "releaseDate",
        "website",
        "summary"
      ]
    },
    "Reference": {
      "$id": "Reference",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "reference": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "name",
        "reference"
      ]
    },
    "Skill": {
      "$id": "Skill",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "level": {
          "type": "string"
        },
        "keywords": {
          "$id": "String[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "required": [
        "name",
        "level",
        "keywords"
      ]
    },
    "Volunteer": {
      "$id": "Volunteer",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "organization": {
          "type": "string"
        },
        "position": {
          "type": "string"
        },
        "website": {
          "type": [
            "string",
            "null"
          ]
        },
        "startDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "endDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        },
        "highlights": {
          "$id": "String[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "required": [
        "organization",
        "position",
        "website",
        "startDate",
        "endDate",
        "summary",
        "highlights"
      ]
    },
    "Work": {
      "$id": "Work",
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "company": {
          "type": "string"
        },
        "position": {
          "type": "string"
        },
        "website": {
          "type": [
            "string",
            "null"
          ]
        },
        "startDate": {
          "type": "string"
        },
        "endDate": {
          "type": [
            "string",
            "null"
          ]
        },
        "summary": {
          "type": [
            "string",
            "null"
          ]
        },
        "highlights": {
          "$id": "String[]",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "required": [
        "company",
        "position",
        "website",
        "startDate",
        "endDate",
        "summary",
        "highlights"
      ]
    }
  },
  "type": "object",
  "properties": {
    "basics": {
      "$ref": "Basics"
    },
    "work": {
      "$id": "Work[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Work"
      }
    },
    "volunteer": {
      "$id": "Volunteer[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Volunteer"
      }
    },
    "education": {
      "$id": "Education[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Education"
      }
    },
    "awards": {
      "$id": "Award[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Award"
      }
    },
    "publications": {
      "$id": "Publication[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Publication"
      }
    },
    "skills": {
      "$id": "Skill[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Skill"
      }
    },
    "languages": {
      "$id": "Language[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Language"
      }
    },
    "interests": {
      "$id": "Interest[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Interest"
      }
    },
    "references": {
      "$id": "Reference[]",
      "type": [
        "array",
        "null"
      ],
      "items": {
        "$ref": "Reference"
      }
    }
  },
  "required": [
    "basics",
    "work",
    "volunteer",
    "education",
    "awards",
    "publications",
    "skills",
    "languages",
    "interests",
    "references"
  ]
}

如何让架构生成器遵守 Required 属性?

JSchemaGenerator has a property DefaultRequired:

Gets or sets the default required state of schemas.

出于某种原因,此 属性 的默认值为 Required.AllowNull,如 source:

所示
public JSchemaGenerator()
{
    _schemaReferenceHandling = SchemaReferenceHandling.Objects;
    _defaultRequired = Required.AllowNull;
}

如果将其更改为 Required.Default, only those properties explicitly marked as required, e.g. with [Required] or [JsonProperty(Required = Required.Always)],将在架构中按要求列出:

var generator = new JSchemaGenerator
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName,
    DefaultRequired = Required.Default,
};

DefaultRequired 的默认值不是 documented, but probably should be. You might open an issue,Newtonsoft 要求对文档进行说明。

演示 fiddle here.