JsonReferenceResolver
Resolves JSON Pointer references.
using Namotion.Reflection;
using Newtonsoft.Json;
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 JsonSchemaAppender _schemaAppender;
private readonly Dictionary<string, IJsonReference> _resolvedObjects = new Dictionary<string, IJsonReference>();
public JsonReferenceResolver(JsonSchemaAppender schemaAppender)
{
_schemaAppender = schemaAppender;
}
public static Func<JsonSchema, JsonReferenceResolver> CreateJsonReferenceResolverFactory(ITypeNameGenerator typeNameGenerator)
{
return (JsonSchema schema) => new JsonReferenceResolver(new JsonSchemaAppender(schema, typeNameGenerator));
}
public void AddDocumentReference(string documentPath, IJsonReference schema)
{
_resolvedObjects[documentPath.Contains("://") ? documentPath : DynamicApis.GetFullPath(documentPath)] = schema;
}
public async Task<IJsonReference> ResolveReferenceAsync(object rootObject, string jsonPath)
{
return await ResolveReferenceAsync(rootObject, jsonPath, true).ConfigureAwait(false);
}
public async Task<IJsonReference> ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath)
{
return await ResolveReferenceAsync(rootObject, jsonPath, false).ConfigureAwait(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 JsonSchema.FromFileAsync(filePath, (JsonSchema schema) => this).ConfigureAwait(false);
}
public virtual async Task<IJsonReference> ResolveUrlReferenceAsync(string url)
{
return await JsonSchema.FromUrlAsync(url, (JsonSchema 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 text = (rootObject as IDocumentPathProvider)?.DocumentPath;
if (text != null) {
if (text.StartsWith("http://") || text.StartsWith("https://"))
return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(new Uri(new Uri(text), jsonPath).ToString(), jsonPath, append).ConfigureAwait(false);
return await ResolveFileReferenceWithAlreadyResolvedCheckAsync(DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(text), 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 = DynamicApis.GetFullPath(arr[0]);
if (!_resolvedObjects.ContainsKey(filePath)) {
IJsonReference jsonReference = await ResolveFileReferenceAsync(filePath).ConfigureAwait(false);
jsonReference.DocumentPath = jsonPath;
_resolvedObjects[filePath] = jsonReference;
}
IJsonReference referencedFile = _resolvedObjects[filePath];
IJsonReference jsonReference2 = (arr.Length != 1) ? (await ResolveReferenceAsync(referencedFile, arr[1]).ConfigureAwait(false)) : referencedFile;
if ((jsonReference2 is JsonSchema) & append) {
JsonSchema obj = _schemaAppender.RootObject as JsonSchema;
if (obj == null || !obj.Definitions.Values.Contains(referencedFile)) {
string typeNameHint = jsonPath.Split('/', '\\').Last().Split(new char[1] {
'.'
})
.First();
_schemaAppender.AppendSchema((JsonSchema)jsonReference2, typeNameHint);
}
}
return 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 JsonSchema) & append)
_schemaAppender.AppendSchema((JsonSchema)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 = obj as IJsonReference;
if (jsonReference != null && jsonReference.Reference != null) {
IJsonReference jsonReference2 = ResolveDocumentReferenceWithoutDereferencing(jsonReference.Reference, segments, checkedObjects);
if (jsonReference2 == null)
return ResolveDocumentReferenceWithoutDereferencing(obj, segments, checkedObjects);
return jsonReference2;
}
return ResolveDocumentReferenceWithoutDereferencing(obj, segments, checkedObjects);
}
private IJsonReference ResolveDocumentReferenceWithoutDereferencing(object obj, List<string> segments, HashSet<object> checkedObjects)
{
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) {
bool? nullable = jsonExtensionObject.ExtensionData?.ContainsKey(text);
bool flag = true;
if ((nullable.GetValueOrDefault() == flag) & nullable.HasValue)
return ResolveDocumentReference(jsonExtensionObject.ExtensionData[text], segments.Skip(1).ToList(), checkedObjects);
}
foreach (ContextualMemberInfo item in from p in ContextualTypeExtensions.GetContextualPropertiesAndFields(obj.GetType())
where p.GetTypeAttribute<JsonIgnoreAttribute>() == null
select p) {
if (item.GetName() == text) {
object value = item.GetValue(obj);
return ResolveDocumentReference(value, segments.Skip(1).ToList(), checkedObjects);
}
}
}
return null;
}
}
}