NjsonSchema Validation If Property1 is equal to Something then require Property2 - c#
I seem to not be able to get const or enum working as part of an if-then-else JSON schema validation.
They seem to be interchangeable when 1 validation value is concerned. (Reference)
Here is my JSON schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Test_Schema",
"description": "A schema for validating a test object",
"type": "object",
"additionalProperties": false,
"properties": {
"GeneralData": {
"type": "object",
"description": "Advsor and admin customer information",
"properties": {
"Name": {
"type": [ "string", "null" ],
"description": "Customer's advisor name"
},
"Age": {
"type": [ "string", "null" ],
"description": "Customer's advisor email"
},
"Location": {
"type": [ "string", "null" ],
"description": "The advisor's manager email'"
}
},
"required": [ "Name", "Location", "Age" ]
},
"ClientData": {
"type": "object",
"description": "Customer's information",
"additionalProperties": false,
"properties": {
"Title": {
"type": [ "string", "null" ]
},
"Forename": {
"type": [ "string", "null" ]
},
"Surname": {
"type": [ "string" ]
}
},
"required": [ "Title" ],
"if": {
"properties": {
"Forename": { "enum": [ "Soameonea" ] }
},
"required": [ "Forename" ]
},
"then": { "required": [ "Surname" ] },
"else": false
}
}
}
If Forename = "Someone" I want Surname to be required.
Here is my jsonObject after serialization:
{
"GeneralData": {
"Name": "Not Relevant",
"Age": "Not Relevant",
"Location": "Not Relevant"
},
"ClientData": {
"Title": "SomeTitle",
"Forename": "Someone",
"Surname": null
}
}
Validation code:
internal void ValidateDataObjectOnSchema()
{
var dataObject = PopulateDataObject();
var json = JsonConvert.SerializeObject(dataObject);
var result = GetSchema().Validate(json);
var errors = result.Select(x =>
{
return new Errors() { Title = x.Property, Description = x.Schema.Description };
});
var i = errors;
}
internal JsonSchema GetSchema()
{
return JsonSchema.FromFileAsync("Test/testSchema.json").Result;
}
Right now it still requires Surname, even though the enum Soameonea != Someone and I only require Title. <== Issue1
In the JSON schema if I set "Surname":{"type":["string","null]} then the error disappears and still I don't get the error if I then change the if condition for the Forename enum to "Someone". <== Issue 2
I cannot get a different validation output if I replace Enum with Const, So if I can get one to work I'm sure the other will follow.
I've found several answers to this question (Answer 1), I try to implement the same thing and for some reason, it fails in my case.
What am I missing?
In the JSON schema if I set "Surname":{"type":["string","null]} then the error disappears
A property with a null value still has a value. If you want to say that the property value must not only exist, but not be null, then add "type": "string" to your then condition.
Update:
As #Relequestial mentioned in one of the comments it seems NJson Schema only supports up to Draft 04, this is why if-then-else did not work and implication should be used instead. Below is my solution with an alternative package.
The same code and json work with the package Newtonsoft.Json.Schema (Reference)
Code:
internal void ValidateDataObjectOnSchemaWithNewtonSoftJson()
{
var dataObject = PopulateDataObject();
var settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
};
var jsonDataObjectString = JsonConvert.SerializeObject(dataObject, settings);
JObject jsonDataObject = JObject.Parse(jsonDataObjectString);
var jsonSchemaFile = File.ReadAllText("Test/testSchema.json");
var schema = JSchema.Parse(jsonSchemaFile);
;
IList<ValidationError> messages;
var result = jsonDataObject.IsValid(schema, out messages);
var errors = GetReadableResult(result, messages);
}
private List<Errors> GetReadableResult(bool result, IList<ValidationError> messages)
{
var errors = new List<Errors>();
if (!result)
{
foreach (var error in messages)
{
if (error.ChildErrors.Count > 0)
{
errors.Add(
new Errors()
{
Path = error.ChildErrors.FirstOrDefault()?.Path,
Kind = error.ChildErrors.FirstOrDefault()?.Message
});
}
else
{
errors.Add(new Errors()
{
Path = error.Path,
Kind = error.Message
});
}
}
}
return errors;
}
JsonSchema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Test_Schema",
"description": "A schema for validating a test object",
"type": "object",
"additionalProperties": false,
"properties": {
"GeneralData": {
"type": "object",
"description": "Advsor and admin customer information",
"properties": {
"Name": {
"type": [ "string", "null" ],
"description": "Customer's advisor name"
},
"Age": {
"type": [ "string", "null" ],
"description": "Customer's advisor email"
},
"Location": {
"type": [ "string", "null" ],
"description": "The advisor's manager email'"
}
},
"required": [ "Name", "Location", "Age" ]
},
"ClientData": {
"type": "object",
"description": "Customer's information",
"additionalProperties": false,
"properties": {
"Title": {
"type": [ "string", "null" ]
},
"Forename": {
"type": "string"
},
"Surname": {
"type": [ "string" ]
}
},
"required": [ "Title" ],
"if": {
"properties": {
"Forename": { "enum": [ "Someone" ] }
},
"required": [ "Forename" ]
},
"then": { "required": [ "Surname" ] },
"else": {}
}
}
}
The only addition would be to make the GetReadableResult recursive for Errors which have multiple child items.
Related
Write one test for multiple requests
I have a json from OpenApi that have all Http requests from my project, it looks like that: { "openapi": "3.0.1", "info": { "title": "TestApi", "version": "1.0" }, "paths": { "/api/Test1/test1/{id}": { "get": { "tags": [ "Test1" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "Success" } } } }, "/api/Test2/test2/{id}": { "get": { "tags": [ "Test2" ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "Success" ... I need to create one test that will check only GET requests that all of them return 200. I know how to test one request for needed status code but how to test multiple requests in one test? And how to correctly take them from json?
How to assign same Json property name, one having value and other being null?
In the json response the property "data" is used as a List and in other places in the Json it is used as a string.. when its used in the List the property has some value when its using as a string the value is coming as null.. How to include both scenario here when I am deserialzing and serializing the json.. without running in to exception A member with the name 'data' already exists. Use the JsonPropertyAttribute to specify another name public class Example { [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public List<Datum> Data { get; set; } [JsonProperty("data", NullValueHandling = NullValueHandling.Include)] public string Data { get; set; } } Here is what I tried to use a private property but not able to resolve the issue [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public List<Datum> Data { get; set; } [JsonProperty("data2", NullValueHandling = NullValueHandling.Include)] private List<Datum> Data2 { set { Data = value; } } Here is the Json { "adRoots": null, "allowedTagTypes": [ 0, 1, 4 ], "canEdit": true, "filterConfigData": { "config": [{ "extraData": null, "id": "Endpoints.ID.SearchInProgress", "title": "Search in Progress", "type": 5 }, { "data": null, "extraData": null, "id": "Endpoints.ID.Policies", "source": { "definition": "dbo", "displayColumn": "Name", "keyColumn": "Id", "name": "Policies", "type": 1 }, "title": "Policies", "type": 3 }, { "extraData": null, "id": "Endpoints.ID.MACs", "title": "MAC Addresses", "type": 1 }, { "data": [{ "name": "Endpoint Closed", "value": 11 }, { "name": "Endpoint Completed", "value": 15 }, { "name": "Endpoint Opened", "value": 10 } ], "extraData": null, "id": "Endpoints.ID.ClientActivityState", "source": { "definition": "State", "displayColumn": "Name", "keyColumn": "Id", "name": "ClientActivityState", "type": 2 }, "title": "Client Activity State", "type": 3 }, { "data": [{ "name": "System Alarm", "value": 3 }, { "name": "System Audit", "value": 2 } ], "extraData": null, "id": "Endpoints.ID.AceType", "source": { "definition": "AceType", "displayColumn": "Name", "keyColumn": "Id", "name": "AceType", "type": 2 }, "title": "ACL: ACE Type", "type": 3 }, { "extraData": null, "id": "Endpoints.ID.AceWho", "title": "ACL: Trustee", "type": 1 }, { "data": [{ "name": "Append Data", "value": 4 }, { "name": "Delete", "value": 65536 }, { "name": "Execute", "value": 32 } ], "extraData": null, "id": "Endpoints.ID.AceRights", "source": { "definition": "AceRights", "displayColumn": "Name", "editor": "ViewModel", "keyColumn": "Id", "name": "AceRights", "type": 2 }, "title": "ACL: Authorization", "type": 3 }, { "extraData": null, "id": "Endpoints.ID.FilterTagName", "title": "Tag Name", "type": 1 }, { "data": null, "extraData": null, "id": "Endpoints.ID.FilterTags", "source": { "definition": "dbo", "displayColumn": "Name", "keyColumn": "Id", "name": "Tags", "type": 1 }, "title": "Tags", "type": 3 }, { "extraData": null, "id": "Endpoints.Name.EndpointName", "title": "Endpoint Name", "type": 1 }, { "data": null, "extraData": null, "id": "Endpoints.Version.Version", "title": "Endpoint Version", "type": 1 }, { "extraData": null, "id": "Endpoints.Platform.Platform", "title": "Endpoint Platform", "type": 1 }, { "data": [{ "name": "Desktop", "value": 1 }, { "name": "Server", "value": 2 }, { "name": "Unknown", "value": 0 } ], "extraData": null, "id": "ELPlatformType", "source": { "displayColumn": "Name", "keyColumn": "Id", "name": "PlatformType", "type": 2 }, "title": "Platform Type", "type": 3 }, { "extraData": null, "id": "Endpoints.LastPoll.LastPoll", "title": "Last Poll Time", "type": 2 } ], "pagedListItems": null }, "forests": null, "item": { "disallowed": false, "editPermissions": 0, "endpointsCount": 0, "filter": null, "id": 404, "ldapPaths": null, "name": "tag", "query": null, "type": 0 } }
How to get a JSON.NET JSchema generator to render the additionalProperties attribute in a JSON schema
I am using JSON.NET JSchema Generator to create schemas based on classes decorated with Data Annotation Attributes. I'm using the generator like this: var generator = new JSchemaGenerator(); generator.ContractResolver = new CamelCasePropertyNamesContractResolver(); generator.SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName; var schema = generator.Generate(typeof(myType)); string jsonSchema = schema.ToString(); This generates an example schema like: { "$id": "myType", "definitions": { "mySubType" : { "$id": "mySubType", "type": [ "object", "null" ], "properties": { "name": { "type: "string" } }, "required": [ "name" ] } }, "type": "object", "properties": { "name": { "type": "string" }, "details": { "$ref": "mySubType" } }, "required": [ "name", "details" ] } I want to be able to generate a schema that includes the additional properties attribute for both myType and mySubType, like this: { "$id": "myType", "definitions": { "mySubType" : { "$id": "mySubType", "type": [ "object", "null" ], "properties": { "name": { "type: "string" } }, "required": [ "name" ], "additionalProperties": false } }, "type": "object", "properties": { "name": { "type": "string" }, "details": { "$ref": "mySubClass" } }, "required": [ "name", "details" ], "additionalProperties": false } How can I generate a schema like this using a JSchema generator? Is there a class level data annotation attribute that does this?
A little bit late, but I struggled with that today.. void Main() { var generator = new JSchemaGenerator(); generator.ContractResolver = new CamelCasePropertyNamesContractResolver(); generator.SchemaIdGenerationHandling = SchemaIdGenerationHandling.TypeName; var schema = generator.Generate(typeof(myType)); RejectAdditionalProperties(schema); string jsonSchema = schema.ToString(); } static void RejectAdditionalProperties(JSchema schema) { schema.AllowAdditionalProperties = false; foreach(var s in schema.Properties.Values) RejectAdditionalProperties(s); }
My Json schema has nested arrays and objects. Adding on to the answer that #semera gave: static void RejectAdditionalProperties(JSchema schema) { if(schema.Type == JSchemaType.Object) { schema.AllowAdditionalProperties = false; foreach (var v in schema.Properties.Values) RejectAdditionalProperties(v); } if(schema.Type == JSchemaType.Array) { foreach (var i in schema.Items) RejectAdditionalProperties(i); } }
JSON single property formatting
I have following JSON: { "cap": [ { "type": "test" }, { "type": "test1" }, { "type": "test2", "bla": "tst" } ] } I want every object that have single property to be one line formated { "type": "test" } so the final JSON should be { "cap": [ { "type": "test" }, { "type": "test1" }, { "type": "test2", "bla": "tst" } ] } How to do this in c#?
How can I display a property value infront of each JSON array item?
Here is the JSON string that I am working with. { "id": 1, "title": "A Test", "items": [ { "id": 157, "title": "some article", "type": "Article" }, { "id": 153, "title": "some other article", "type": "Article" } ] } I am using Json.Net for serialization. Is there anyway that I can format the JSON like this before displaying? { "id": 1, "title": "A Test", "items": [ "157" : { "title": "some article", "type": "Article" }, "153" : { "title": "some other article", "type": "Article" } ] } Thanks in advance.
You can get pretty close to the output you want using Json.Net's LINQ-to-JSON API (JObjects) to transform the original JSON. Here is one way to do it: public static string Transform(string json) { JObject root = JObject.Parse(json); JObject itemsObj = new JObject(); foreach (JObject item in root["items"]) { JToken id = item["id"]; id.Parent.Remove(); itemsObj.Add(id.ToString(), item); } root["items"].Parent.Remove(); root.Add("items", itemsObj); return root.ToString(); } If you pass your original JSON to this method, you will get the following output: { "id": 1, "title": "A Test", "items": { "157": { "title": "some article", "type": "Article" }, "153": { "title": "some other article", "type": "Article" } } } Fiddle: https://dotnetfiddle.net/1di41P