JsonReferenceResolver
Resolves JSON Pointer references.
using NJsonSchema.Infrastructure;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NJsonSchema
{
public class JsonReferenceResolver
{
private readonly JsonSchemaResolver _schemaResolver;
private readonly Dictionary<string, JsonSchema4> _resolvedSchemas = new Dictionary<string, JsonSchema4>();
public JsonReferenceResolver(JsonSchemaResolver schemaResolver)
{
_schemaResolver = schemaResolver;
}
public void AddDocumentReference(string documentPath, JsonSchema4 schema)
{
_resolvedSchemas[documentPath] = schema;
}
public async Task<JsonSchema4> ResolveReferenceAsync(object rootObject, string jsonPath)
{
if (jsonPath == "#") {
if (rootObject is JsonSchema4)
return (JsonSchema4)rootObject;
throw new InvalidOperationException("Could not resolve the JSON path '#' because the root object is not a JsonSchema4.");
}
if (jsonPath.StartsWith("#/"))
return ResolveDocumentReference(rootObject, jsonPath);
if (jsonPath.StartsWith("http://") || jsonPath.StartsWith("https://"))
return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(jsonPath, jsonPath).ConfigureAwait(false);
string documentPath = (rootObject as IDocumentPathProvider)?.DocumentPath;
if (documentPath != null) {
if (documentPath.StartsWith("http://") || documentPath.StartsWith("https://"))
return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(new Uri(new Uri(documentPath), jsonPath).ToString(), jsonPath).ConfigureAwait(false);
return await ResolveFileReferenceWithAlreadyResolvedCheckAsync(DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(documentPath), jsonPath), jsonPath).ConfigureAwait(false);
}
throw new NotSupportedException("Could not resolve the JSON path '" + jsonPath + "' because no document path is available.");
}
protected virtual JsonSchema4 ResolveDocumentReference(object rootObject, string jsonPath)
{
List<string> segments = jsonPath.Split(new char[1] {
'/'
}).Skip(1).ToList();
JsonSchema4 jsonSchema = ResolveDocumentReference(rootObject, segments, new HashSet<object>());
if (jsonSchema == null)
throw new InvalidOperationException("Could not resolve the path '" + jsonPath + "'.");
return jsonSchema;
}
protected virtual async Task<JsonSchema4> ResolveFileReferenceAsync(string filePath)
{
return await JsonSchema4.FromFileAsync(filePath, (JsonSchema4 schema) => this).ConfigureAwait(false);
}
protected virtual async Task<JsonSchema4> ResolveUrlReferenceAsync(string url)
{
return await JsonSchema4.FromUrlAsync(url, (JsonSchema4 schema) => this).ConfigureAwait(false);
}
private async Task<JsonSchema4> ResolveFileReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath)
{
try {
string[] arr = Regex.Split(fullJsonPath, "(?=#)");
if (!_resolvedSchemas.ContainsKey(arr[0])) {
JsonSchema4 jsonSchema = await ResolveFileReferenceAsync(arr[0]).ConfigureAwait(false);
_schemaResolver.AppendSchema(jsonSchema, null);
_resolvedSchemas[arr[0]] = jsonSchema;
}
JsonSchema4 jsonSchema2 = _resolvedSchemas[arr[0]];
return (arr.Length != 1) ? (await ResolveReferenceAsync(jsonSchema2, arr[1]).ConfigureAwait(false)) : jsonSchema2;
} catch (Exception innerException) {
throw new InvalidOperationException("Could not resolve the JSON path '" + jsonPath + "' with the full JSON path '" + fullJsonPath + "'.", innerException);
}
}
private async Task<JsonSchema4> ResolveUrlReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath)
{
try {
string[] arr = fullJsonPath.Split(new char[1] {
'#'
});
if (!_resolvedSchemas.ContainsKey(arr[0])) {
JsonSchema4 jsonSchema = await ResolveUrlReferenceAsync(arr[0]).ConfigureAwait(false);
_schemaResolver.AppendSchema(jsonSchema, null);
_resolvedSchemas[arr[0]] = jsonSchema;
}
JsonSchema4 jsonSchema2 = _resolvedSchemas[arr[0]];
return (arr.Length != 1) ? (await ResolveReferenceAsync(jsonSchema2, "#" + arr[1]).ConfigureAwait(false)) : jsonSchema2;
} catch (Exception innerException) {
throw new InvalidOperationException("Could not resolve the JSON path '" + jsonPath + "' with the full JSON path '" + fullJsonPath + "'.", innerException);
}
}
private JsonSchema4 ResolveDocumentReference(object obj, List<string> segments, HashSet<object> checkedObjects)
{
if (obj == null || obj is string || checkedObjects.Contains(obj))
return null;
if (segments.Count == 0)
return obj as JsonSchema4;
checkedObjects.Add(obj);
string text = segments[0];
if (obj is IDictionary) {
if (((IDictionary)obj).Contains(text))
return ResolveDocumentReference(((IDictionary)obj)[text], segments.Skip(1).ToList(), checkedObjects);
} else if (obj is IEnumerable) {
if (int.TryParse(text, out int result)) {
object[] array = ((IEnumerable)obj).Cast<object>().ToArray();
if (array.Length > result)
return ResolveDocumentReference(array[result], segments.Skip(1).ToList(), checkedObjects);
}
} else {
foreach (ReflectionCache.PropertyOrField item in from p in ReflectionCache.GetPropertiesAndFields(obj.GetType())
where p.CustomAttributes.JsonIgnoreAttribute == null
select p) {
if (item.GetName() == text) {
object value = item.GetValue(obj);
return ResolveDocumentReference(value, segments.Skip(1).ToList(), checkedObjects);
}
}
}
return null;
}
}
}