Skip to content

Commit

Permalink
support inaccessible types
Browse files Browse the repository at this point in the history
  • Loading branch information
gregsdennis committed Dec 27, 2023
1 parent 9e1fd20 commit 312fbdf
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 1 deletion.
78 changes: 78 additions & 0 deletions JsonSchema.Tests/Serialization/DeserializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Text.Encodings.Web;
using System.Text.Json;
using Json.Schema.Serialization;
Expand Down Expand Up @@ -41,13 +42,28 @@ private class FooWithSchema
)
.Required(nameof(Foo.Value));

public static readonly JsonSchema PointSchema =
new JsonSchemaBuilder()
.Type(SchemaValueType.Object)
.Properties(
("X", new JsonSchemaBuilder().Type(SchemaValueType.Integer)),
("Y", new JsonSchemaBuilder().Type(SchemaValueType.Integer))
)
.AdditionalProperties(false);

private static readonly JsonSerializerOptions _options = new()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new ValidatingJsonConverter { OutputFormat = OutputFormat.List } }
};

[SetUp]
public void Setup()
{
ValidatingJsonConverter.MapType<Point>(PointSchema);
}

/// <summary>
/// Demonstrates that even without a schema, the incorrect JSON data type is
/// caught by the serializer. However, the error is only thrown for the first
Expand Down Expand Up @@ -291,6 +307,68 @@ public void WithSchema_MultipleDeserializations()
throw;
}
}

[Test]
public void InaccessibleType_Valid()
{
try
{
var jsonText = @"{
""X"": 4,
""Y"": 5
}";

var options = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new ValidatingJsonConverter { OutputFormat = OutputFormat.List } }
};

var model = JsonSerializer.Deserialize<Point>(jsonText, options);

Console.WriteLine(JsonSerializer.Serialize(model, options));
}
catch (Exception e)
{
HandleException(e);
throw;
}
}

[Test]
public void InaccessibleType_Invalid()
{
Assert.Throws<JsonException>(() =>
{
try
{
var jsonText = @"{
""X"": ""string"",
""Y"": 5
}";

var options = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new ValidatingJsonConverter { OutputFormat = OutputFormat.List } }
};

var model = JsonSerializer.Deserialize<Point>(jsonText, options);

Console.WriteLine(JsonSerializer.Serialize(model, options));
}
catch (Exception e)
{
HandleException(e);
Assert.AreEqual("JSON does not meet schema requirements", e.Message);
throw;
}
}
);
}

/// <summary>
/// The validation result is passed in the <see cref="Exception.Data"/>
/// property under the `"validation"` key.
Expand Down
14 changes: 13 additions & 1 deletion JsonSchema/Serialization/ValidatingJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Json.Schema.Serialization;
public class ValidatingJsonConverter : JsonConverterFactory
{
private static readonly ConcurrentDictionary<Type, JsonConverter?> _cache = new();
private static readonly ValidatingJsonConverter _instance = new();

/// <summary>
/// Specifies the output format.
Expand All @@ -29,6 +30,11 @@ public class ValidatingJsonConverter : JsonConverterFactory
/// </summary>
public bool? RequireFormatValidation { get; set; }

public static void MapType<T>(JsonSchema schema)
{
_instance.CreateConverter(typeof(T), schema);
}

/// <summary>When overridden in a derived class, determines whether the converter instance can convert the specified object type.</summary>
/// <param name="typeToConvert">The type of the object to check whether it can be converted by this converter instance.</param>
/// <returns>
Expand Down Expand Up @@ -57,7 +63,13 @@ public override bool CanConvert(Type typeToConvert)
if (_cache.TryGetValue(typeToConvert, out var converter)) return converter;

var schemaAttribute = (JsonSchemaAttribute)typeToConvert.GetCustomAttributes(typeof(JsonSchemaAttribute)).Single();
var schema = schemaAttribute.Schema;

return CreateConverter(typeToConvert, schema);
}

private JsonConverter? CreateConverter(Type typeToConvert, JsonSchema schema)
{
var converterType = typeof(ValidatingJsonConverter<>).MakeGenericType(typeToConvert);

// ReSharper disable once ConvertToLocalFunction
Expand All @@ -71,7 +83,7 @@ public override bool CanConvert(Type typeToConvert)
}
return newOptions;
};
converter = (JsonConverter)Activator.CreateInstance(converterType, schemaAttribute.Schema, optionsFactory);
var converter = (JsonConverter)Activator.CreateInstance(converterType, schema, optionsFactory);

var validatingConverter = (IValidatingJsonConverter)converter;
validatingConverter.OutputFormat = OutputFormat ?? Schema.OutputFormat.Flag;
Expand Down
2 changes: 2 additions & 0 deletions json-everything.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/GrammarAndSpelling/GrammarChecking/RulesStates/=LanguageTool_002EEN_002ELANGUAGE_005FTOOL/@EntryIndexedValue">DisabledByUser</s:String>
<s:String x:Key="/Default/GrammarAndSpelling/GrammarChecking/RulesStates/=LanguageTool_002EEN_002ELANGUAGETOOL/@EntryIndexedValue">DisabledByUser</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=antimultiples/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=codegen/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Contexted/@EntryIndexedValue">True</s:Boolean>
Expand Down

0 comments on commit 312fbdf

Please sign in to comment.