Skip to content

Commit 47339fd

Browse files
committed
Additional optimizations, tests added now.
1 parent 0fc522e commit 47339fd

File tree

3 files changed

+95
-32
lines changed

3 files changed

+95
-32
lines changed

JSONAPI.Tests/Core/ModelManagerTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,34 @@ public void DoesntFindMissingId()
3636
// Assert
3737
Assert.Fail("An InvalidOperationException should be thrown and we shouldn't get here!");
3838
}
39+
40+
[TestMethod]
41+
public void GetPropertyMapTest()
42+
{
43+
// Arrange
44+
// Act
45+
var propMap = ModelManager.Instance.GetPropertyMap(typeof(Post));
46+
47+
// Assert
48+
Assert.AreSame(typeof(Post).GetProperty("Id"), propMap["id"]);
49+
Assert.AreSame(typeof(Post).GetProperty("Author"), propMap["author"]);
50+
}
51+
52+
[TestMethod]
53+
public void GetJsonKeyForTypeTest()
54+
{
55+
// Arrange
56+
var pluralizationService = new PluralizationService();
57+
58+
// Act
59+
var postKey = ModelManager.Instance.GetJsonKeyForType(typeof(Post), pluralizationService);
60+
var authorKey = ModelManager.Instance.GetJsonKeyForType(typeof(Author), pluralizationService);
61+
var commentKey = ModelManager.Instance.GetJsonKeyForType(typeof(Comment), pluralizationService);
62+
63+
// Assert
64+
Assert.AreEqual("posts", postKey);
65+
Assert.AreEqual("authors", authorKey);
66+
Assert.AreEqual("comments", commentKey);
67+
}
3968
}
4069
}

JSONAPI/Core/ModelManager.cs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace JSONAPI.Core
1010
{
11-
class ModelManager
11+
internal class ModelManager
1212
{
1313
#region Singleton pattern
1414

@@ -38,6 +38,11 @@ private Lazy<Dictionary<Type, Dictionary<string, PropertyInfo>>> _propertyMaps
3838
() => new Dictionary<Type, Dictionary<string, PropertyInfo>>()
3939
);
4040

41+
private Lazy<Dictionary<Type, string>> _jsonKeysForType
42+
= new Lazy<Dictionary<Type, string>>(
43+
() => new Dictionary<Type, string>()
44+
);
45+
4146
#endregion
4247

4348
#region Id property determination
@@ -75,20 +80,58 @@ public Dictionary<string, PropertyInfo> GetPropertyMap(Type type)
7580

7681
var propMapCache = _propertyMaps.Value;
7782

78-
if (propMapCache.TryGetValue(type, out propMap)) return propMap;
79-
80-
propMap = new Dictionary<string, PropertyInfo>();
81-
PropertyInfo[] props = type.GetProperties();
82-
foreach (PropertyInfo prop in props)
83+
lock (propMapCache)
8384
{
84-
propMap[JsonApiFormatter.FormatPropertyName(prop.Name)] = prop;
85-
}
85+
if (propMapCache.TryGetValue(type, out propMap)) return propMap;
86+
87+
propMap = new Dictionary<string, PropertyInfo>();
88+
PropertyInfo[] props = type.GetProperties();
89+
foreach (PropertyInfo prop in props)
90+
{
91+
propMap[JsonApiFormatter.FormatPropertyName(prop.Name)] = prop;
92+
}
8693

87-
propMapCache.Add(type, propMap);
94+
propMapCache.Add(type, propMap);
95+
}
8896

8997
return propMap;
9098
}
9199

92100
#endregion
101+
102+
//TODO: This has been "moved" here so we can cache the results and improve performance...but
103+
// it raises the question of whether the various methods called within here should belong
104+
// to JsonApiFormatter at all...should they move here also? Should the IPluralizationService
105+
// instance belong to ModelManager instead?
106+
internal string GetJsonKeyForType(Type type, IPluralizationService pluralizationService)
107+
{
108+
string key = null;
109+
110+
var keyCache = _jsonKeysForType.Value;
111+
112+
lock (keyCache)
113+
{
114+
if (keyCache.TryGetValue(type, out key)) return key;
115+
116+
if (JsonApiFormatter.IsMany(type))
117+
type = JsonApiFormatter.GetSingleType(type);
118+
119+
var attrs = type.CustomAttributes.Where(x => x.AttributeType == typeof(Newtonsoft.Json.JsonObjectAttribute)).ToList();
120+
121+
string title = type.Name;
122+
if (attrs.Any())
123+
{
124+
var titles = attrs.First().NamedArguments.Where(arg => arg.MemberName == "Title")
125+
.Select(arg => arg.TypedValue.Value.ToString()).ToList();
126+
if (titles.Any()) title = titles.First();
127+
}
128+
129+
key = JsonApiFormatter.FormatPropertyName(pluralizationService.Pluralize(title));
130+
131+
keyCache.Add(type, key);
132+
}
133+
134+
return key;
135+
}
93136
}
94137
}

JSONAPI/Json/JsonApiFormatter.cs

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public override Task WriteToStreamAsync(System.Type type, object value, Stream w
106106

107107
//writer.Formatting = Formatting.Indented;
108108

109-
var root = GetPropertyName(type, value);
109+
var root = GetJsonKeyForType(type, value);
110110

111111
writer.WriteStartObject();
112112
writer.WritePropertyName(root);
@@ -155,7 +155,9 @@ protected void Serialize(object value, Stream writeStream, JsonWriter writer, Js
155155
var idProp = ModelManager.Instance.GetIdProperty(value.GetType());
156156
writer.WriteValue(GetValueForIdProperty(idProp, value));
157157

158-
PropertyInfo[] props = value.GetType().GetProperties();
158+
// Leverage the cached map to avoid another costly call to GetProperties()
159+
PropertyInfo[] props = ModelManager.Instance.GetPropertyMap(value.GetType()).Values.ToArray();
160+
159161
// Do non-model properties first, everything else goes in "links"
160162
//TODO: Unless embedded???
161163
IList<PropertyInfo> modelProps = new List<PropertyInfo>();
@@ -293,8 +295,9 @@ protected void Serialize(object value, Stream writeStream, JsonWriter writer, Js
293295
if (lt == null)
294296
throw new JsonSerializationException(
295297
"A property was decorated with SerializeAs(SerializeAsOptions.Link) but no LinkTemplate attribute was provided.");
296-
string link = String.Format(lt, objId,
297-
value.GetType().GetProperty("Id").GetValue(value, null));
298+
string link = String.Format(lt, objId,
299+
GetIdFor(value)); //value.GetType().GetProperty("Id").GetValue(value, null));
300+
298301
//writer.WritePropertyName(ContractResolver.FormatPropertyName(prop.Name));
299302
writer.WriteStartObject();
300303
writer.WritePropertyName("href");
@@ -399,7 +402,7 @@ protected void SerializeLinkedResources(Stream writeStream, JsonWriter writer, J
399402
foreach (KeyValuePair<Type, KeyValuePair<JsonWriter, StringWriter>> apair in writers)
400403
{
401404
apair.Value.Key.WriteEnd(); // close off the array
402-
writer.WritePropertyName(GetPropertyName(apair.Key));
405+
writer.WritePropertyName(GetJsonKeyForType(apair.Key));
403406
writer.WriteRawValue(apair.Value.Value.ToString()); // write the contents of the type JsonWriter's StringWriter to the main JsonWriter
404407
}
405408

@@ -422,7 +425,7 @@ private object ReadFromStream(Type type, Stream readStream, HttpContent content,
422425
{
423426
object retval = null;
424427
Type singleType = GetSingleType(type);
425-
var pripropname = GetPropertyName(type);
428+
var pripropname = GetJsonKeyForType(type);
426429
var contentHeaders = content == null ? null : content.Headers;
427430

428431
// If content length is 0 then return default value for this type
@@ -749,22 +752,10 @@ private object DeserializePrimitive(Type type, JsonReader reader)
749752

750753
#endregion
751754

752-
private string GetPropertyName(Type type, dynamic value = null)
755+
//TODO: Could be expensive, and is called often...move to ModelManager and cache result?
756+
private string GetJsonKeyForType(Type type, dynamic value = null)
753757
{
754-
if (IsMany(type))
755-
type = GetSingleType(type);
756-
757-
var attrs = type.CustomAttributes.Where(x => x.AttributeType == typeof(Newtonsoft.Json.JsonObjectAttribute)).ToList();
758-
759-
string title = type.Name;
760-
if (attrs.Any())
761-
{
762-
var titles = attrs.First().NamedArguments.Where(arg => arg.MemberName == "Title")
763-
.Select(arg => arg.TypedValue.Value.ToString()).ToList();
764-
if (titles.Any()) title = titles.First();
765-
}
766-
767-
return FormatPropertyName(this.PluralizationService.Pluralize(title));
758+
return ModelManager.Instance.GetJsonKeyForType(type, this.PluralizationService);
768759
}
769760

770761
//private string GetPropertyName(Type type)
@@ -780,14 +771,14 @@ private bool IsMany(dynamic value = null)
780771
return false;
781772
}
782773

783-
private bool IsMany(Type type)
774+
internal static bool IsMany(Type type)
784775
{
785776
return
786777
type.IsArray ||
787778
(type.GetInterfaces().Contains(typeof(IEnumerable)) && type.IsGenericType);
788779
}
789780

790-
private Type GetSingleType(Type type)//dynamic value = null)
781+
internal static Type GetSingleType(Type type)//dynamic value = null)
791782
{
792783
if (IsMany(type))
793784
if (type.IsGenericType)

0 commit comments

Comments
 (0)