JsonReferenceResolver
Resolves JSON Pointer references.
using NJsonSchema.Infrastructure;
using NJsonSchema.References;
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, IJsonReference> _resolvedObjects = new Dictionary<string, IJsonReference>();
public JsonReferenceResolver(JsonSchemaResolver schemaResolver)
{
_schemaResolver = schemaResolver;
}
public void AddDocumentReference(string documentPath, IJsonReference schema)
{
_resolvedObjects[documentPath] = schema;
}
public async Task<IJsonReference> ResolveReferenceAsync(object rootObject, string jsonPath)
{
return await ResolveReferenceAsync(rootObject, jsonPath, true);
}
public async Task<IJsonReference> ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath)
{
return await ResolveReferenceAsync(rootObject, jsonPath, false);
}
public virtual IJsonReference ResolveDocumentReference(object rootObject, string jsonPath)
{
List<string> segments = jsonPath.Split(new char[1] {
'/'
}).Skip(1).ToList();
IJsonReference jsonReference = ResolveDocumentReference(rootObject, segments, new HashSet<object>());
if (jsonReference == null)
throw new InvalidOperationException("Could not resolve the path '" + jsonPath + "'.");
return jsonReference;
}
public virtual async Task<IJsonReference> ResolveFileReferenceAsync(string filePath)
{
return await JsonSchema4.FromFileAsync(filePath, (JsonSchema4 schema) => this).ConfigureAwait(false);
}
public virtual async Task<IJsonReference> ResolveUrlReferenceAsync(string url)
{
return await JsonSchema4.FromUrlAsync(url, (JsonSchema4 schema) => this).ConfigureAwait(false);
}
private async Task<IJsonReference> ResolveReferenceAsync(object rootObject, string jsonPath, bool append)
{
if (jsonPath == "#") {
if (rootObject is IJsonReference)
return (IJsonReference)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, append).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, append).ConfigureAwait(false);
return await ResolveFileReferenceWithAlreadyResolvedCheckAsync(DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(documentPath), jsonPath), jsonPath, append).ConfigureAwait(false);
}
throw new NotSupportedException("Could not resolve the JSON path '" + jsonPath + "' because no document path is available.");
}
private async Task<IJsonReference> ResolveFileReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath, bool append)
{
try {
string[] arr = Regex.Split(fullJsonPath, "(?=#)");
string filePath = arr[0];
if (!_resolvedObjects.ContainsKey(filePath)) {
IJsonReference jsonReference = await ResolveFileReferenceAsync(filePath).ConfigureAwait(false);
jsonReference.DocumentPath = jsonPath;
if ((jsonReference is JsonSchema4) & append)
_schemaResolver.AppendSchema((JsonSchema4)jsonReference, filePath.Split('/', '\\').Last().Split(new char[1] {
'.'
})
.First());
_resolvedObjects[filePath] = jsonReference;
}
IJsonReference jsonReference2 = _resolvedObjects[filePath];
return (arr.Length != 1) ? (await ResolveReferenceAsync(jsonReference2, arr[1]).ConfigureAwait(false)) : jsonReference2;
} catch (Exception innerException) {
throw new InvalidOperationException("Could not resolve the JSON path '" + jsonPath + "' with the full JSON path '" + fullJsonPath + "'.", innerException);
}
}
private async Task<IJsonReference> ResolveUrlReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath, bool append)
{
try {
string[] arr = fullJsonPath.Split(new char[1] {
'#'
});
if (!_resolvedObjects.ContainsKey(arr[0])) {
IJsonReference jsonReference = await ResolveUrlReferenceAsync(arr[0]).ConfigureAwait(false);
jsonReference.DocumentPath = jsonPath;
if ((jsonReference is JsonSchema4) & append)
_schemaResolver.AppendSchema((JsonSchema4)jsonReference, null);
_resolvedObjects[arr[0]] = jsonReference;
}
IJsonReference jsonReference2 = _resolvedObjects[arr[0]];
return (arr.Length != 1) ? (await ResolveReferenceAsync(jsonReference2, "#" + arr[1]).ConfigureAwait(false)) : jsonReference2;
} catch (Exception innerException) {
throw new InvalidOperationException("Could not resolve the JSON path '" + jsonPath + "' with the full JSON path '" + fullJsonPath + "'.", innerException);
}
}
private IJsonReference ResolveDocumentReference(object obj, List<string> segments, HashSet<object> checkedObjects)
{
if (obj == null || obj is string || checkedObjects.Contains(obj))
return null;
IJsonReference jsonReference;
if ((jsonReference = (obj as IJsonReference)) != null && jsonReference.Reference != null)
obj = jsonReference.Reference;
if (segments.Count == 0)
return obj as IJsonReference;
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 {
IJsonExtensionObject jsonExtensionObject = obj as IJsonExtensionObject;
if (jsonExtensionObject != null && jsonExtensionObject.ExtensionData?.ContainsKey(text) == true)
return ResolveDocumentReference(jsonExtensionObject.ExtensionData[text], segments.Skip(1).ToList(), checkedObjects);
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;
}
}
}