diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index 9076ebf42f..5082938a23 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -13,36 +13,36 @@ on:
workflow_dispatch:
jobs:
- check-format:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Setup .NET Core
- uses: actions/setup-dotnet@v2
- with:
- # See https://stackoverflow.com/a/71619953/878701
- dotnet-version: 6.0.100
- - id: dotnet-format
- run: |
- dotnet format --verify-no-changes --report bin || true
- FORMAT_REPORT=$(cat bin/format-report.json)
- if [ "${FORMAT_REPORT}" != "[]" ]; then
- echo 'Expand to see formatting issues
' >> pr-message.md
- echo >> pr-message.md
- echo '```json' >> pr-message.md
- cat bin/format-report.json >> pr-message.md
- echo >> pr-message.md
- echo '```' >> pr-message.md
- echo >> pr-message.md
- echo ' ' >> pr-message.md
- echo "::set-output name=POST_REPORT::true"
- fi
- - if: ${{ steps.dotnet-format.outputs.POST_REPORT == 'true' }}
- uses: machine-learning-apps/pr-comment@master
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- path: pr-message.md
+ # check-format:
+ # runs-on: ubuntu-latest
+ # steps:
+ # - uses: actions/checkout@v3
+ # - name: Setup .NET Core
+ # uses: actions/setup-dotnet@v2
+ # with:
+ # # See https://stackoverflow.com/a/71619953/878701
+ # dotnet-version: 6.0.100
+ # - id: dotnet-format
+ # run: |
+ # dotnet format --verify-no-changes --report bin || true
+ # FORMAT_REPORT=$(cat bin/format-report.json)
+ # if [ "${FORMAT_REPORT}" != "[]" ]; then
+ # echo 'Expand to see formatting issues
' >> pr-message.md
+ # echo >> pr-message.md
+ # echo '```json' >> pr-message.md
+ # cat bin/format-report.json >> pr-message.md
+ # echo >> pr-message.md
+ # echo '```' >> pr-message.md
+ # echo >> pr-message.md
+ # echo ' ' >> pr-message.md
+ # echo "::set-output name=POST_REPORT::true"
+ # fi
+ # - if: ${{ steps.dotnet-format.outputs.POST_REPORT == 'true' }}
+ # uses: machine-learning-apps/pr-comment@master
+ # env:
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # with:
+ # path: pr-message.md
build:
runs-on: ubuntu-latest
steps:
diff --git a/JsonSchema.Tests/UnrecognizedKeywordTests.cs b/JsonSchema.Tests/UnrecognizedKeywordTests.cs
new file mode 100644
index 0000000000..a108a6fc72
--- /dev/null
+++ b/JsonSchema.Tests/UnrecognizedKeywordTests.cs
@@ -0,0 +1,46 @@
+using System.Linq;
+using System.Text.Json;
+using Json.More;
+using NUnit.Framework;
+
+namespace Json.Schema.Tests;
+
+public class UnrecognizedKeywordTests
+{
+ [Test]
+ public void FooIsNotAKeyword()
+ {
+ var schemaText = "{\"foo\": \"bar\"}";
+
+ var schema = JsonSerializer.Deserialize(schemaText);
+
+ Assert.AreEqual(1, schema!.Keywords!.Count);
+ Assert.IsInstanceOf(schema.Keywords.First());
+ }
+
+ [Test]
+ public void FooProducesAnAnnotation()
+ {
+ var schemaText = "{\"foo\": \"bar\"}";
+
+ var schema = JsonSerializer.Deserialize(schemaText);
+
+ var result = schema!.Validate("{}", new ValidationOptions { OutputFormat = OutputFormat.Detailed });
+
+ Assert.IsTrue(result.IsValid);
+ Assert.AreEqual(1, result.Annotations.Count());
+ Assert.IsTrue("bar".AsJsonElement().IsEquivalentTo((JsonElement)result.Annotations.First().Value));
+ }
+
+ [Test]
+ public void FooIsIncludedInSerialization()
+ {
+ var schemaText = "{\"foo\":\"bar\"}";
+
+ var schema = JsonSerializer.Deserialize(schemaText);
+
+ var reText = JsonSerializer.Serialize(schema);
+
+ Assert.AreEqual(schemaText, reText);
+ }
+}
\ No newline at end of file
diff --git a/JsonSchema/JsonSchema.cs b/JsonSchema/JsonSchema.cs
index fa4a9d6508..60e405bca4 100644
--- a/JsonSchema/JsonSchema.cs
+++ b/JsonSchema/JsonSchema.cs
@@ -9,6 +9,8 @@
using Json.More;
using Json.Pointer;
+#pragma warning disable CS0618
+
namespace Json.Schema;
///
@@ -21,23 +23,28 @@ public class JsonSchema : IRefResolvable, IEquatable
///
/// The empty schema {}
. Functionally equivalent to .
///
- public static readonly JsonSchema Empty = new JsonSchema(Enumerable.Empty(), null);
+ public static readonly JsonSchema Empty = new(Enumerable.Empty(), null);
///
/// The true
schema. Passes all instances.
///
- public static readonly JsonSchema True = new JsonSchema(true);
+ public static readonly JsonSchema True = new(true);
///
/// The false
schema. Fails all instances.
///
- public static readonly JsonSchema False = new JsonSchema(false);
+ public static readonly JsonSchema False = new(false);
///
/// Gets the keywords contained in the schema. Only populated for non-boolean schemas.
///
public IReadOnlyCollection? Keywords { get; }
///
- /// Gets other non-keyword (or unknown keyword) properties in the schema.
+ /// (obsolete) Gets other non-keyword (or unknown keyword) properties in the schema.
///
+ ///
+ /// This property is now obsolete and no longer used. It will be removed at the next major version.
+ /// Until then, it will remain populated.
+ ///
+ [Obsolete("Unrecognized keyword data now appears as UnrecognizedKeyword instances in the Keywords collection.")]
public IReadOnlyDictionary? OtherData { get; }
///
@@ -293,22 +300,15 @@ public void ValidateSubschema(ValidationContext context)
break;
}
- if (newResolvable == null)
+ if (newResolvable is UnrecognizedKeyword unrecognized)
{
- // TODO: document that this process does not consider `$id` in extraneous data
- if (resolvable is JsonSchema { OtherData: { } } subSchema &&
- subSchema.OtherData.TryGetValue(segment.Value, out var element))
- {
- var newPointer = JsonPointer.Create(pointer.Segments.Skip(i + 1), true);
- var value = newPointer.Evaluate(element);
- var asSchema = FromText(value.ToString());
- return (asSchema, currentUri);
- }
-
- return (null, currentUri);
+ var newPointer = JsonPointer.Create(pointer.Segments.Skip(i + 1), true);
+ var value = newPointer.Evaluate(unrecognized.Value);
+ var asSchema = FromText(value.ToString());
+ return (asSchema, currentUri);
}
- resolvable = newResolvable;
+ resolvable = newResolvable!;
}
return (resolvable as JsonSchema, currentUri);
@@ -431,6 +431,9 @@ public override JsonSchema Read(ref Utf8JsonReader reader, Type typeToConvert, J
using var document = JsonDocument.ParseValue(ref reader);
var element = document.RootElement;
otherData[keyword] = element.Clone();
+
+ var unrecognizedKeyword = new UnrecognizedKeyword(keyword, element);
+ keywords.Add(unrecognizedKeyword);
break;
}
@@ -473,15 +476,6 @@ public override void Write(Utf8JsonWriter writer, JsonSchema value, JsonSerializ
JsonSerializer.Serialize(writer, keyword, keyword.GetType(), options);
}
- if (value.OtherData != null)
- {
- foreach (var data in value.OtherData)
- {
- writer.WritePropertyName(data.Key);
- JsonSerializer.Serialize(writer, data.Value, options);
- }
- }
-
writer.WriteEndObject();
}
}
diff --git a/JsonSchema/JsonSchema.csproj b/JsonSchema/JsonSchema.csproj
index e30ae1e3f4..0e3ee3b110 100644
--- a/JsonSchema/JsonSchema.csproj
+++ b/JsonSchema/JsonSchema.csproj
@@ -12,8 +12,8 @@
https://github.com/gregsdennis/json-everything
https://github.com/gregsdennis/json-everything
json-schema validation schema json
- 2.3.0
- 2.3.0.0
+ 2.4.0
+ 2.4.0.0
2.0.0.0
LICENSE
JsonSchema.Net
diff --git a/JsonSchema/JsonSchemaBuilderExtensions.cs b/JsonSchema/JsonSchemaBuilderExtensions.cs
index 701918d39c..e00a6ea83c 100644
--- a/JsonSchema/JsonSchemaBuilderExtensions.cs
+++ b/JsonSchema/JsonSchemaBuilderExtensions.cs
@@ -1060,6 +1060,32 @@ public static JsonSchemaBuilder UniqueItems(this JsonSchemaBuilder builder, bool
return builder;
}
+ ///
+ /// Adds a keyword that's not recognized by any vocabulary - extra data - to the schema.
+ ///
+ /// The builder.
+ /// The keyword name.
+ /// The value.
+ /// The builder.
+ public static JsonSchemaBuilder Unrecognized(this JsonSchemaBuilder builder, string name, JsonElement value)
+ {
+ builder.Add(new UnrecognizedKeyword(name, value));
+ return builder;
+ }
+
+ ///
+ /// Adds a keyword that's not recognized by any vocabulary - extra data - to the schema.
+ ///
+ /// The builder.
+ /// The keyword name.
+ /// The value.
+ /// The builder.
+ public static JsonSchemaBuilder Unrecognized(this JsonSchemaBuilder builder, string name, JsonElementProxy value)
+ {
+ builder.Add(new UnrecognizedKeyword(name, value));
+ return builder;
+ }
+
///
/// Add an `$vocabulary` keyword.
///
diff --git a/JsonSchema/KeywordExtensions.cs b/JsonSchema/KeywordExtensions.cs
index b374a66ba3..624128f8e5 100644
--- a/JsonSchema/KeywordExtensions.cs
+++ b/JsonSchema/KeywordExtensions.cs
@@ -15,7 +15,8 @@ public static class KeywordExtensions
.GetTypes()
.Where(t => typeof(IJsonSchemaKeyword).IsAssignableFrom(t) &&
!t.IsAbstract &&
- !t.IsInterface)
+ !t.IsInterface &&
+ t != typeof(UnrecognizedKeyword))
.ToDictionary(t => t, t => t.GetCustomAttribute().Name);
///
@@ -29,6 +30,8 @@ public static string Keyword(this IJsonSchemaKeyword keyword)
{
if (keyword == null) throw new ArgumentNullException(nameof(keyword));
+ if (keyword is UnrecognizedKeyword unrecognized) return unrecognized.Name;
+
var keywordType = keyword.GetType();
if (!_attributes.TryGetValue(keywordType, out var name))
{
diff --git a/JsonSchema/UnrecognizedKeyword.cs b/JsonSchema/UnrecognizedKeyword.cs
new file mode 100644
index 0000000000..ca159613a8
--- /dev/null
+++ b/JsonSchema/UnrecognizedKeyword.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Json.More;
+
+namespace Json.Schema;
+
+///
+/// Handles unrecognized keywords.
+///
+[SchemaDraft(Draft.Draft6)]
+[SchemaDraft(Draft.Draft7)]
+[SchemaDraft(Draft.Draft201909)]
+[SchemaDraft(Draft.Draft202012)]
+[JsonConverter(typeof(UnrecognizedKeywordJsonConverter))]
+public class UnrecognizedKeyword : IJsonSchemaKeyword, IEquatable
+{
+ ///
+ /// The name or key of the keyword.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The value of the keyword.
+ ///
+ public JsonElement Value { get; }
+
+ ///
+ /// Creates a new .
+ ///
+ /// The name of the keyword.
+ /// Whether items should be unique.
+ public UnrecognizedKeyword(string name, JsonElement value)
+ {
+ Name = name;
+ Value = value.Clone();
+ }
+
+ ///
+ /// Provides validation for the keyword.
+ ///
+ /// Contextual details for the validation process.
+ public void Validate(ValidationContext context)
+ {
+ context.EnterKeyword(Name);
+ context.LocalResult.SetAnnotation(Name, Value);
+ context.LocalResult.Pass();
+ context.ExitKeyword(Name, context.LocalResult.IsValid);
+ }
+
+ /// Indicates whether the current object is equal to another object of the same type.
+ /// An object to compare with this object.
+ /// true if the current object is equal to the other parameter; otherwise, false.
+ public bool Equals(UnrecognizedKeyword? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Name == other.Name && Value.IsEquivalentTo(other.Value);
+ }
+
+ /// Determines whether the specified object is equal to the current object.
+ /// The object to compare with the current object.
+ /// true if the specified object is equal to the current object; otherwise, false.
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as UnrecognizedKeyword);
+ }
+
+ /// Serves as the default hash function.
+ /// A hash code for the current object.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Name.GetHashCode() * 397) ^ Value.GetHashCode();
+ }
+ }
+}
+
+internal class UnrecognizedKeywordJsonConverter : JsonConverter
+{
+ public override UnrecognizedKeyword Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException("Unrecognized keywords should be handled manually during JsonSchema deserialization.");
+ }
+
+ public override void Write(Utf8JsonWriter writer, UnrecognizedKeyword value, JsonSerializerOptions options)
+ {
+ writer.WritePropertyName(value.Name);
+ writer.WriteValue(value.Value);
+ }
+}
\ No newline at end of file
diff --git a/json-everything.net/wwwroot/md/release-notes/json-schema.md b/json-everything.net/wwwroot/md/release-notes/json-schema.md
index 2ac4a438e1..e0cedd806e 100644
--- a/json-everything.net/wwwroot/md/release-notes/json-schema.md
+++ b/json-everything.net/wwwroot/md/release-notes/json-schema.md
@@ -1,3 +1,7 @@
+# [2.4.0](https://github.com/gregsdennis/json-everything/pull/270)
+
+Added `UnrecognizedKeyword` to represent keywords that were not recognized by any known vocabulary. The values of these keywords are then captured in the validation results as annotations. As a result of this change `JsonSchema.OtherData` has been marked obsolete.
+
# [2.3.0](https://github.com/gregsdennis/json-everything/pull/249)
[#190](https://github.com/gregsdennis/json-everything/issues/190) - Added support for custom and localized error messages.