C# Client Library
A C# Client Library for the AnalyzeRe REST API
Loading...
Searching...
No Matches
ReferenceConverter.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Concurrent;
3using System.Linq;
4using System.Reflection;
5
8using Newtonsoft.Json;
9using Newtonsoft.Json.Linq;
10
12{
15 public class ReferenceConverter : JsonConverter
16 {
17 #region Configuration Options
36
41 #endregion Configuration Options
42
43 #region Constructor
55 #endregion Constructor
56
57 #region Constants
58 private const string RefIdPropertyName = nameof(IReference.ref_id);
59 private const string HrefPropertyName = nameof(IReference.href);
60 private const string ReferenceTypePropertyName = nameof(IReference.reference_type);
61 #endregion Constants
62
67 private static readonly ConcurrentDictionary<Type, bool> CanConvertCache =
69
70 #region Public Methods
77 public override bool CanConvert(Type objectType)
78 {
79 return CanConvertCache.GetOrAdd(objectType, type =>
80 type.IsSubclassOf(typeof(Reference)) ||
81 type.IsSubclassOfRawGeneric(typeof(IReference<>)));
82 }
83
86 private static bool TryGetStringProperty(JObject jObject, string propertyName, out string value)
87 {
88 if (!jObject.TryGetValue(propertyName, out JToken token))
89 {
90 value = null;
91 return false;
92 }
93 value = token.Type == JTokenType.Null ? null :
94 token.Type == JTokenType.String ? token.Value<string>() :
95 throw new JsonReaderException($"Expected the {propertyName} value " +
96 $"to be a JSON \"string\" but a {token.Type.ToString()} was returned ({token})");
97 return true;
98 }
99
108 public override object ReadJson(
110 Type objectType,
111 object existingValue,
113 {
114 if (reader.TokenType == JsonToken.Null)
115 return null;
116 // Load JObject from stream
118
121 {
122 // By default, set the reference type based on the object we're populating
123 Type referenceInnerType = objectType.IsGenericType ?
124 objectType.GetGenericArguments()[0] : typeof(IAPIResource);
125
126 // Start out assuming this reference is potentially inlined if this json has any attributes
127 bool isReferenceInlined = jObject.Any<JToken>();
128 // Attempt to extract each of the "pure-reference" fields we might encounter
129 // If *any* of these fields are present, then we know this reference is not inlined.
130 isReferenceInlined &= !TryGetStringProperty(jObject, ReferenceTypePropertyName, out string strReferenceType);
131 isReferenceInlined &= !TryGetStringProperty(jObject, RefIdPropertyName, out string strRefId);
132 isReferenceInlined &= !TryGetStringProperty(jObject, HrefPropertyName, out string strHref);
133
134 // If possible, try to pick a more specific reference type to instantiate.
135 // This will only have a value if the reference is resolved, inlined,
136 // and contains a derived instance of a polymorphic type
137 Type runtimeType = null;
139 {
140 if (TryGetStringProperty(jObject, APIType_Polymorphic.TypePropertyName, out string strType) &&
141 !String.IsNullOrEmpty(strType))
143 }
144 else
145 {
146 // See if the reference json indicated its reference type
147 if (!String.IsNullOrEmpty(strReferenceType))
149 // Otherwise, if an href was provided, determine the type based on it
150 else if (!String.IsNullOrEmpty(strHref))
152 }
153
154 // If any of the above approaches reveal a more specific runtime type,
155 // attempt to use it when instantiating our Reference object.
156 if (runtimeType != null)
157 {
158 // Make sure that if the resolved inner reference type is generic, we resolve
159 // its generic arguments to some covariantly-valid type.
160 if (runtimeType.IsGenericType)
162 // Ensure this new type is assignable to the required type before replacing it
163 if (referenceInnerType.IsAssignableFrom(runtimeType))
165 }
166
167 // Produce the type of the reference object we must construct.
168 Type referenceType = typeof(Reference<>).MakeGenericTypeFast(referenceInnerType);
169
170 // Ensure that the resolved reference type is compatible with the target property type.
171 if (!objectType.IsAssignableFrom(referenceType))
172 throw new InvalidOperationException("The provided reference type: '" +
173 referenceType.NiceTypeName() + "' did not match the expected type: '" +
174 objectType.NiceTypeName() + "'");
175
176 object newReference;
177 // Easy case, the server just sent us a reference object
179 {
181 new[] { typeof(string), typeof(string) });
182 if (referenceConstructor == null)
183 throw new Exception("Missing Reference Constructor " +
184 referenceType.NiceTypeName() + "(string, string)");
185 // Construct the new reference using the deserialized data.
187 new object[] { strRefId, strHref });
188 }
189 // Hard case, the server sent us an 'inlined'/'resolved' object, which we
190 // will have to wrap in a Reference object to fit our object model.
191 else
192 {
193 // Create the generic constructor that will be used to
194 // construct our new reference object.
196 referenceType.GetConstructor(new[] { referenceInnerType });
197 if (newReferenceConstructor == null)
198 throw new Exception("Could not find a constructor for '" +
199 referenceType.NiceTypeName() + "' taking a parameter of type '" +
200 referenceInnerType.NiceTypeName() + "'");
201
202 // Create the inner object that will be passed to the reference constructor
204
205 // Construct the new reference object, passing the contained
206 // value to the constructor
208 }
209 return newReference;
210 }
211 }
212
215 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
216 {
217 if (value == null)
218 {
219 writer.WriteNull();
220 return;
221 }
222
223 bool resolveReference = false;
225 throw new InvalidOperationException($"Unhandled case for ReferenceSerializer. " +
226 $"Unexpected non-Reference value: {value}");
227
228 // If the object is null, or we've been told explicitly to use references only,
229 // do not resolve references.
230 // If we are told to resolve references, or we were told to expand references
231 // if they are resolved, and this one is resolved, or this one is resolved and it has
232 // no ref_id (meaning it cannot be represented as a reference) resolveReference = true
233 if (SerializationMethod == ReferenceSerializationMethod.ExpandReferenceValues ||
234 (reference.resolved && (reference.ref_id == null ||
236 resolveReference = true;
237
238 // If resolve reference is set to true, serialize the entire reference value,
239 // otherwise, serialize the reference using the base serializer
241 serializer.Serialize(writer, reference.GetValue());
242 else
243 {
244 // We want to serialize using the current serializer settings,
245 // but use the default serializing behaviour (to avoid infinite recursion).
246 JsonSerializerSettings recurseSettings = serializer.GetJsonSerializerSettings();
247 recurseSettings.Converters.Remove(
248 serializer.Converters.OfType<ReferenceConverter>().Cast<JsonConverter>().Single());
250 }
251 }
252 #endregion Public Methods
253 }
254}
Base class used by all types and resources.
Describes a collection of resources which can be listed.
static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
Creates a new reader for the specified jObject by copying the settings from an existing reader.
Deserializes AnalyzeRe API Reference objects to a new instance of a the target generic Reference type...
override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
Serializes a Reference object based on the configured SerializationMethod controlling whether/when re...
ReferenceSerializationMethod SerializationMethod
Used during serialization to determine whether references should be expanded in JSON.
override bool CanConvert(Type objectType)
Determines if this converter is designed for deserialization to objects of the specified type.
ReferenceSerializationMethod
The SerializationMethod to use when serializing references.
@ UseReferencesOnly
Use the JSON representation of a reference to the object.
@ ExpandReferenceValues
Retrieve the referenced object and serialize it in place of the reference.
@ ExpandIfResolved
Serialize the referenced object only if it has already been retrieved.
override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
Parses the json to the specified type.
ReferenceConverter(ReferenceSerializationMethod serializationMethod=ReferenceSerializationMethod.UseReferencesOnly)
Initializes the ReferenceConverter using the specified SerializationMethod.
Implements the reference entity interface, with support for resolving references using lazy loading.
Utility for resolving types given only the types name (Useful for parsing ambiguous JSON objects).
static Type ResolveBaseTypeByURL(string href)
Get the base type of object referenced by the supplied server URL.
static bool ResolveTypeByName(string typeName, out Type resolvedType, Type requiredBaseType=null)
Clever method that checks all assemblies in the application being compiled for the type referenced by...
static Type GetGenericCovariantBase(Type T)
Returns the most strongly typed covariant base type which is assignable from the specified type T.
Interface for Base class used by all resources.
Base interface for all reference entities.
string ref_id
The id of the object being referred to.
Definition IReference.cs:17
string reference_type
In cases where a model can reference resources belonging to one of a number of collections,...
Definition IReference.cs:13
string href
The HREF that can be used to retrieve the object being referred to.
Definition IReference.cs:22