Using Json.Net Schema Validation with Multiple Levels of Sub-Schema References - c#

I have a Json Schema that has a relative file reference, like this:
{
"$id": "TestPacket",
"title": "TestPacket",
"type": "object",
"properties": {
"Header": {
"$ref": "../../TestSchema/Test/TestHeader.json#"
},
"Body": {
"$ref": "../../TestSchema/Test/Test.json#"
}
}
Test.json also has a relative file reference:
{
"$id": "Test",
"title": "Test",
"type": "object",
"properties": {
"Group": {
"title": "Group",
"type": "string"
},
"Child": {
"$ref": "../../TestSchema/Test/Child.json#"
}
},
"required": [
"Version",
"Group"
]}
Both Quicktype and XMLSpy are able to successfully parse this (I have tried many, many different methods beyond the "../../folder/folder" pattern, and this works best for what we're going for).
My problem arises when I try to use Json.Net Schema Validation. Currently, we are embedding the json into the assembly and using a JSchemaPreloadedResolver to resolve them, like this:
JSchemaPreloadedResolver resolver = new JSchemaPreloadedResolver();
resolver.Add(new Uri(TestSchema/Test/Test.json", UriKind.RelativeOrAbsolute, assembly.GetManifestResourceStream("SchemaTests.TestSchema.Test.Test.json"));
resolver.Add(new Uri(TestSchema/Test/Child.json", UriKind.RelativeOrAbsolute, assembly.GetManifestResourceStream("SchemaTests.TestSchema.Test.Child.json"));
resolver.Add(new Uri(TestSchema/Test/TestPacket.json", UriKind.RelativeOrAbsolute, assembly.GetManifestResourceStream("SchemaTests.TestSchema.Test.TestPacket.json")); resolver.Add(new Uri(TestSchema/Test/TestHeader.json", UriKind.RelativeOrAbsolute, assembly.GetManifestResourceStream("SchemaTests.TestSchema.Test.TestHeader.json"));
When I load a JSchema from a JsonReader using this resolver, it works great, as long as there is no 2nd sub-schema reference. In fact, in this example, the TestHeader.json parses, but it fails when it comes to the Test.json. If I include Child into Test.json as a definition instead of a relative reference, it also passes.
I had a similar problem using JSchemaReaderSettings with BaseUri set to the root folder. I eventually realized that it would successfully resolve the first reference, but then the BaseUri would be moved to the Test.Json location when trying to resolve the 2nd reference.
I doubt that's the problem here as Add() simply adds the reference string and stream to a dictionary for lookup. It looks like to me that it shouldn't matter what is in the resolver URI and the schema $ref URI as long as they match.
My problem always occurs when I attempt to resolve the reference of a schema that is itself referenced. Any advice?

Ok, I figured out what I was doing wrong. According to json-schema.org:
The $id property is a URI that serves two purposes:
1) It declares a unique identifier for the schema.
2)It declares a base URI against which $ref URIs are resolved.
Because I had an $id property in my sub-schemas (Test.json), it was changing the base URI to that location when it was parsed. This caused the next reference in the sub-schema to be incorrect.
When I remove the $id property in all schemas except the top-level schema, all schema now parse correctly.

Related

How do I import a Json file into a django database whilst maintaining object references

I have a C# desktop app and a django webapp that share a set of common class/model types. The C# app is exporting json files containing instances of these models, and I am trying to import these into the django database.
The complication is that the parent model contained within the json file has properties that may reference the same sub-model in multiple places. For example, the json file looks something like this:
{
"$id": "1",
"SubModels": {
"$id": "2",
"$values": [
{
"$id": "3",
"name": "Dave",
"value": "123"
},
{
"$id": "4",
"name": "John",
"value": "42"
}
]
},
"PreferredSubModel: {
"$ref": "4"
}
}
Which was created using the using System.Text.Json.Serialization C# library with the ReferenceHandler = ReferenceHandler.Preserve serialisation option. In django, I've converted the json file into a dictionary using model_dictionary = JSONParser().parse(json_file).
Are there any existing functions (in the django Python environment) that can handle this $id/$ref system to maintain class instances, or do I need to code my own deserializer? If the latter, does anyone have any suggestions for the best way to handle it?
I'm new to django and json files, so hopefully I've just been googling the wrong terms and something exists...

AdaptiveCards throws an exception during .NET parsing

I currently encountered the following problem parsing an adaptive card.
This is the card:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "{{DATE(${$root.AdditionalData['DUE-DATE']},COMPACT)}}",
"wrap": true
}
]
}
This is the card-content:
{
"AdditionalData": {
"DUE-DATE": "2021-09-10T16:29:59Z"
}
}
Code:
c# on .NET Framework 4.7.2 where layout is a string with the above card and content is a string with the above card-content:
AdaptiveCardTemplate template = new AdaptiveCardTemplate(layout);
string cardJson = template.Expand(content);
AdaptiveCardParseResult card = AdaptiveCard.FromJson(cardJson);
And it crashes with:
AdaptiveCards.AdaptiveSerializationException: 'Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.'
JsonReaderException: Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.
The generated JSON on cardJson looks wrong to me at the text property:
{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.4","body":[{"type":"TextBlock","text":,"wrap":true}]}
I'm using the adaptive cards nuget packages:
AdaptiveCards 2.7.2
AdaptiveCards.Templating 1.2.
Did I encounter a parsing bug? The value for the text property should be 10.9.2021.
In the designer on adaptivecards.io everything works fine for some reason. Does anyone have a fix/workaround?
If you want the literal "text":"10.9.2021" to appear in your cardJson, use "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}" to generate the required value for your TextBlock:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
"wrap": true
}
]
}
This causes all date formatting to be performed by the AdaptiveCardTemplate and results in:
{
"type":"AdaptiveCard",
"$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
"version":"1.4",
"body":[
{
"type":"TextBlock",
"text":"10.9.2021",
"wrap":true
}
]
}
Demo fiddle #1 here.
If you would prefer "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}" in your cardJson which delegates date formatting to the TextBlock, use:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "{{DATE(${AdditionalData['DUE-DATE']}, COMPACT)}}",
"wrap": true
}
]
}
Which results in
"text": "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}",
Demo fiddle #2 here.
Notes:
According to the Microsoft documentation:
Use Dot-notation to access sub-objects of an object hierarchy. E.g., ${myParent.myChild}
Use Indexer syntax to retrieve properties by key or items in an array. E.g., ${myArray[0]}
But, when accessing an object property with a hyphen (or some other reserved operator) in its name, it is apparently necessary to use the Indexer syntax ['DUE-DATE'] instead of Dot‑notation to retrieve its value, passing the property name inside a single-quoted string as the indexer.
According to the docs
There are a few reserved keywords to access various binding scopes. ...
"$root": "The root data object. Useful when iterating to escape to parent object",
Thus you do not need to use $root when accessing properties of the currently scoped object (which is, by default, the root) when using Dot-notation. If for whatever reason you need or want to address the root object directly, you may use $root like so:
"text": "${formatDateTime($root.AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
Demo fiddle #3 here.
However, it seems that using $root in combination with {{DATE()}} causes malformed JSON to be generated. I.e.
"text": "{{DATE(${$root.AdditionalData['DUE-DATE']}, COMPACT)}}",
results in "text":, as indicated in your question.
Demo fiddle #4 here.
This looks to be a bug in the framework. Possibly the parser is choking on the sequence of tokens ${$, as your issue somewhat resembles Issue #6026: [Authoring][.NET][Templating] Inconsistency in accessing $root inside a $when property in adaptive card templating which reports a failure to parse "$when": "${$root.UserName != null}" correctly.
You can avoid the problem either by omitting $root entirely, or by wrapping $root.AdditionalData['DUE-DATE'] in an additional formatDateTime() like so:
"text": "{{DATE(${formatDateTime($root.AdditionalData['DUE-DATE'])}, COMPACT)}}",
Resulting in
"text": "{{DATE(2021-09-10T16:29:59.000Z, COMPACT)}}",
Demo fiddle #5 here.
From the documentation page Adaptive Card Templating SDKs: Troubleshooting:
Q. Why date/time in RFC 3389 format e.g "2017-02-14T06:08:00Z" when used with template doesn't works with TIME/DATE functions?
A. .NET sdk nuget version 1.0.0-rc.0 exhibits this behavior. this behavior is corrected in the subsequent releases... Please use formatDateTime() function to format the date/time string to RFC 3389 as seen in this example, or you can bypass TIME/DATE functions, and just use formatDateTime(). For more information on formatDateTime(), please go here.
While this recommendation to use formatDateTime was to fix a problem in 1.0.0-rc.0, the trick also resolves the issue mentioned in note #2 above.

NSwag: Generation of a C# client results in invalid code

I am trying to generate a C# client for the Trello API. Therefore I have downloaded the Open API specification from https://developer.atlassian.com/cloud/trello/swagger.v3.json and run the following nswag command from the bash.
nswag openapi2csclient /input:swagger.v3.json \
/classname:TrelloClient \
/namespace:Integrations.Trello \
/output:TrelloClient.cs \
/GenerateClientInterfaces:True \
/GenerateExceptionClasses:True \
/GenerateClientClasses:True \
/DisposeHttpClient:False \
/OperationGenerationMode:SingleClientFromOperationId
The code generation completes without errors, but the generated code does not compile because it contains many errors. For example, some of the generated method names contain invalid expressions like =idAsync, or method signatures have ambiguous parameters (for instance, multiple key and token parameters of type string). The following method declaration has been generated for the GetMembers method, which is obviously the wrong syntax.
System.Threading.Tasks.Task<Member> GetMembers=idAsync(
string key, string token, string id, string actions, string boards, BoardBackgrounds? boardBackgrounds,
BoardsInvited? boardsInvited, BoardFields? boardsInvited_fields, bool? boardStars, string cards,
CustomBoardBackgrounds? customBoardBackgrounds, CustomEmoji2? customEmoji, CustomStickers? customStickers,
MemberFields? fields, string notifications, Organizations? organizations,
OrganizationFields? organization_fields, bool? organization_paid_account,
OrganizationsInvited? organizationsInvited, OrganizationFields? organizationsInvited_fields,
bool? paid_account, bool? savedSearches, Tokens? tokens);
Are there any special options that need to be set when processing an Open API specification document of version 3?
I was able to solve syntax issues regarding the get-members method in the generated code by changing the OperationGenerationMode from SingleClientFromOperationId to SingleClientFromPathSegments. This works as a workaround because the =id term only appears in the operationId.
I inspected Trello´s Open API specification document and found out that the definition of get-members contained the =id term in the operationId; not sure if this is wrong in the spec, or the generator is not able to handle that case correctly. The definition of the get-members method looks like that (foreshortened):
... "/members/{id}":
{
"get":
{
"tags": [],
"operationId":"get-members=id",
"parameters" ...
}
}
As a sidenote, the reason why I use the SingleClient... operation modes is that Nswag also adds GeneratedCode attributes to any partial class and interface which also results in invalid code (this attribute can only be applied once). The SingleClient... operation mode solves that.
What remains is the problem that NSwag generates methods with duplicated parameters, which has been reported as an issue, but is not solved yet; see https://github.com/RicoSuter/NSwag/issues/2560 for further information. Last but not least, I tried to remove the =id from the operationId and switched back to the SingleClientFromOperationId operation mode to check whether this does the trick, which is not the case.
I assume that NSwag is used by a broad audience which makes me think that this must be more related to the processed spec than Nswag. Thus, I looked up the spec for methods that have been reported by the C# compiler to have duplicated parameters. Here is an example.
...
{
"name": "token",
"in": "query",
"description": "The API token to use",
"required": true,
"schema": {
"$ref": "#/components/schemas/APIToken"
}
},
{
"name": "token",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "string"
}
}
...
So it seems that a method can have parameters of the same name, but they can appear in different places such a the query-string, or be part of the URL path. Then, it might be Nswag that produces the wrong output; if it is intended that both parameters can be sent to the API, then the generator should just prefix the names to avoid ambiguities, for instance, queryToken and pathToken, leaving it up to the developer to decide what parameter to use or to give a hint to the expected value (in case both parameters are required and expect different values).

JObject/JToken doesnt Log on NLog

I have a class and some of properties are dynamic, when my class is pass to some action API the method undestand as a JObject, the problem is to Log on NLOG, when I try to do that, my log from my properties Dynamic shows up "[]" example bellow:
myProperty:[
[[]],
[],
[]
]
When I try convert all over my class Objet to JObject(Newtonsoft) all my log will be [] like bellow:
{ "time": "2019-05-13 18:12:16.2224", "level": "DEBUG", "JsonProperties": { "log": [[[[[]],[[]],[[]],[[[[]],[[]],[[]]]],[[[[[[[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]]]]],[[[[]],[[]],[[]],[[]],[[]],[[[[]],[[]],[[]]]],[[[[[]],[[]]],[[[]],[[]]]]],[[[[[]],[[]],[[]]],[[[]],[[]],[[]]]]],[[[[[]],[[]]],[[[]],[[]]]]]]]]]]]],[[[[[]],[[]],[[]],[[[[]],[[]],[[]],[[]]]]]]]] }, "message": "Testando performance no LoggerGenerator.", "log": [[],[]] }
I solved this problem converts JObject to Dictionary and Works perfectly, the problem is overhead to do that.
I need to solve this problem without create my own method and converts to Dictionary
Thanks.
JObject is an IEnumerable and NLog will try to enumerate it.
You can do the following:
logger.Info("Hello {0}", jObject); // No structured logging, becomes string.Format
logger.Info("Hello {$myobj}", jObject); // Structured logging that forces JObject.ToString
logger.Info("Hello {myobj}", jObject.ToString()); // Converts to string upfront
You can also customize how NLog handles special objects (Like JObject) by overriding these:
NLog.Config.ConfigurationItemFactory.Default.ValueFormatter
NLog.Config.ConfigurationItemFactory.Default.JsonConverter

How to create URL parameters from JSON structure in C#

:) For my app I need to generate URL for HTTP GET Request. I've prepared JSON data, because I will be working with them, so I would like to generate these URLs for filtering data from my JSON data structures in C# code.
For example I have this JSON structure:
{
"filter": {
"logic": "and",
"filters": [
{
"field": "created",
"operator": "gte",
"value": "2016-09-19+00:00:00"
},
{
"field": "created",
"operator": "lte",
"value": "2016-09-19+59:59:59"
}
]
}
}
I would like to have this URL:
.../api/v6/tickets?filter[logic]=and&filter[filters][0][field]=created&filter[filters][0][operator]=gte&filter[filters][0][value]=2016-09-19+00:00:00&filter[filters][1][field]=created&filter[filters][1][operator]=lte&filter[filters][1][value]=2016-09-19+59:59:59
I thought that there could be some method in HttpWebRequest to do that for me, but I didn't find anything. I've tried to pass on JSON data in body, but it's not working (because I need to create Request before that?).
Do you know something (some standardized method or something else) what I could use for generating dynamically URL by JSON structure?
Thank you very much!

Categories

Resources