Permalink
Browse files

Added a basic JSON parser to Expando

  • Loading branch information...
1 parent 5ec9bf4 commit 1f471994676923775f459b3c633b4cd8cc3bf334 @atheken committed Jun 20, 2010
View
78 NoRM.Tests/JSONParser.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Xunit;
+using Norm.BSON;
+
+namespace NoRM.Tests
+{
+ public class JSONParserTests
+ {
+ private ObjectParser _parser = new ObjectParser();
+
+ [Fact]
+ public void ParserCanParseEmptyObject()
+ {
+ var result = _parser.ParseJSON("{}");
+ Assert.NotNull(result);
+
+ }
+
+ [Fact]
+ public void ParserCanParseMemberAndNull()
+ {
+ var result = _parser.ParseJSON("{\"Hello\":null}");
+ Assert.Null(result["Hello"]);
+ }
+
+ [Fact]
+ public void ParserCanParseMemberAndBool()
+ {
+ var result = _parser.ParseJSON("{\"Hello\": true }");
+ Assert.Equal(true, result["Hello"]);
+ }
+
+ [Fact]
+ public void ParserCanParseMemberAndNumber()
+ {
+ var result = _parser.ParseJSON("{\"Pi\": -3.1415 , \"Pie\" : 314e-2 }");
+ Assert.Equal(-3.1415d, result["Pi"]);
+ Assert.Equal(3.14d, result["Pie"]);
+ }
+
+ [Fact]
+ public void ParserCanParseMemberAndString()
+ {
+ var result = _parser.ParseJSON("{\"Hello\": \"World\" }");
+ Assert.Equal("World", result["Hello"]);
+ }
+
+ [Fact]
+ public void ParserCanParseArray()
+ {
+ var result = _parser.ParseJSON(@"{""Hello"": [1,2,3]}");
+ var values = (Object[])result["Hello"];
+ Assert.True(values.SequenceEqual(new object[] { 1d, 2d, 3d }));
+ }
+
+ [Fact]
+ public void ParserCanParseNestedObjects()
+ {
+ var result = _parser.ParseJSON(@"{""Hello"": {""a"": 1}, ""World"" : {""b"": { ""52"":""bomber"" } } }");
+ var nestedObject1 = (Expando)result["Hello"];
+ Assert.Equal(1d, nestedObject1["a"]);
+ var nestedObject2 = (Expando)result["World"];
+ Assert.Equal("bomber", ((Expando)nestedObject2["b"])["52"]);
+ }
+
+ [Fact]
+ public void ParserCanParseMultipleMembers()
+ {
+ var results = _parser.ParseJSON("{\"Hello\":1,\"World\":false}");
+ Assert.Equal(1d, results["Hello"]);
+ Assert.Equal(false, results["World"]);
+ }
+
+ }
+}
View
16 NoRM.Tests/MongoConfigurationTests.cs
@@ -276,5 +276,21 @@ public void Subclassed_Type_Is_Returned_When_Superclass_Is_Used_For_The_Collecti
Assert.Equal(typeof(SubClassedObjectFluentMapped), found.ElementAt(0).GetType());
}
}
+
+ [Fact]
+ public void Can_fluently_configure_discriminator_for_all_implementations_of_an_interface()
+ {
+ MongoConfiguration.Initialize(r => r.AddMap<DiscriminationMap>());
+
+ using (var mongo = Mongo.Create(TestHelper.ConnectionString()))
+ {
+ var obj1 = new InterfacePropertyContainingClass();
+ mongo.GetCollection<InterfacePropertyContainingClass>().Insert(obj1);
+ var found = mongo.GetCollection<InterfacePropertyContainingClass>().Find();
+
+ Assert.Equal(1, found.Count());
+ Assert.Equal(typeof(NotDiscriminatedClass), found.ElementAt(0).InterfaceProperty.GetType());
+ }
+ }
}
}
View
1 NoRM.Tests/NoRM.Tests.csproj
@@ -95,6 +95,7 @@
<Compile Include="GridFS\GridFileCollectionTests.cs" />
<Compile Include="Helpers\QueryTestHelper.cs" />
<Compile Include="IdPropertyFinderTests.cs" />
+ <Compile Include="JSONParser.cs" />
<Compile Include="LinqTests\LinqAggregates.cs" />
<Compile Include="LinqTests\LinqDeepGraph.cs" />
<Compile Include="LinqTests\LinqTests.cs" />
View
31 NoRM.Tests/TestClasses.cs
@@ -597,6 +597,37 @@ public InterfaceDiscriminatedClass()
public Guid Id { get; protected set; }
}
+ internal class InterfacePropertyContainingClass
+ {
+ public InterfacePropertyContainingClass()
+ {
+ Id = Guid.NewGuid();
+ InterfaceProperty = new NotDiscriminatedClass();
+ }
+
+ [MongoIdentifier]
+ public Guid Id { get; set; }
+ public INotDiscriminated InterfaceProperty { get; set; }
+ }
+
+ internal interface INotDiscriminated
+ {
+ string Something { get; set; }
+ }
+
+ internal class NotDiscriminatedClass : INotDiscriminated
+ {
+ public string Something { get; set; }
+ }
+
+ public class DiscriminationMap : MongoConfigurationMap
+ {
+ public DiscriminationMap()
+ {
+ For<INotDiscriminated>(config => config.UseAsDiscriminator());
+ }
+ }
+
internal interface IDTOWithNonDefaultId
{
[MongoIdentifier]
View
12 NoRM/BSON/BSONSerializer.cs
@@ -156,12 +156,10 @@ private void WriteObject(object document)
var typeHelper = ReflectionHelper.GetHelperForType(document.GetType());
var idProperty = typeHelper.FindIdProperty();
var documentType = document.GetType();
- var discriminator = typeHelper.GetTypeDiscriminator();
+ var discriminator = GetTypeDiscriminator(typeHelper, documentType);
if (String.IsNullOrEmpty(discriminator) == false)
- {
SerializeMember("__type", discriminator);
- }
foreach (var property in typeHelper.GetProperties())
{
@@ -188,6 +186,14 @@ private void WriteObject(object document)
}
}
+ private string GetTypeDiscriminator(ReflectionHelper typeHelper, Type documentType)
+ {
+ var discriminator = typeHelper.GetTypeDiscriminator();
+ if (String.IsNullOrEmpty(discriminator))
+ discriminator = MongoConfiguration.GetTypeDiscriminator(documentType);
+ return discriminator;
+ }
+
/// <summary>
/// Serializes a member.
/// </summary>
View
115 NoRM/BSON/ObjectParser.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Norm.BSON
+{
+ /// <summary>
+ /// Provides a mechanism for parsing JSON into an Expando.
+ /// </summary>
+ public class ObjectParser
+ {
+ private static readonly Regex _rxObject = new Regex(@"\s*{\s*(?<obj>.*)\s*}\s*(,|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static readonly Regex _rxArray = new Regex("\\s*\\[\\s*(?<arrayValues>.*)\\s*]\\s*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ /// <summary>
+ /// deals with most of the possible quote escapes, but a few remain.
+ /// </summary>
+ private static readonly Regex _rxPair = new Regex(@"\s*""(?<key>.*?)((?<!\\)(?<!\\)"")\s*:\s*(?<value>(((?'Open'\[)[^[]*)(?'Close-Open']))|((.*?)|(""(.*?)((?<!\\)(?<!\\)""))))\s*(,|$)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private static readonly Regex _rxArrayMember = new Regex(@"\s*(?<value>(((?'Open'\[)[^[]*)(?'Close-Open']))|((.*?)|(""(.*?)((?<!\\)(?<!\\)""))))\s*(,|$)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private static readonly Regex _rxBool = new Regex(@"^\s*(true)|(false)\s*(,|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private static readonly Regex _rxNull = new Regex(@"^\s*null\s*(,|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ private static readonly Regex _rxNumber = new Regex(@"^\s*-?\s*(([0-9]*[.]?[0-9]*)|([0-9]+))\s*(e(\+|-)?[0-9]+)?\s*(,|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ /// <summary>
+ /// Convert a string to an IExpando.
+ /// </summary>
+ /// <exception cref="">Throws an exception when the string passed in cannot be parsed.</exception>
+ /// <param name="jsonToParse"></param>
+ /// <returns></returns>
+ public Expando ParseJSON(String jsonToParse)
+ {
+ Expando retval = new Expando();
+ var memberstring = _rxObject.Match(jsonToParse).Groups["obj"].Value;
+ Match m;
+ do
+ {
+ m = _rxPair.Match(memberstring);
+ if(m.Success)
+ {
+ retval[m.Groups["key"].Value] = this.ParseMember(m.Groups["value"].Value);
+ memberstring = memberstring.Remove(0, m.Length);
+ }
+
+ } while (m.Success && memberstring.Length > 0);
+ return retval;
+ }
+
+ private Object[] ParseJSONArray(String jsonToParse)
+ {
+ var retval = new List<Object>();
+ var memberstring = _rxArray.Match(jsonToParse).Groups["arrayValues"].Value;
+ Match m;
+ do
+ {
+ m = _rxArrayMember.Match(memberstring);
+ if (m.Success)
+ {
+ retval.Add(this.ParseMember(m.Groups["value"].Value));
+ memberstring = memberstring.Remove(0, m.Length);
+ }
+
+ } while (m.Success && memberstring.Length > 0);
+ return retval.ToArray();
+ }
+
+ private Object ParseMember(String member)
+ {
+ member = member ?? "";
+ object retval = null;
+ if (_rxObject.IsMatch(member))
+ {
+ //construct object.
+ retval = this.ParseJSON(member);
+ }
+ else if (_rxArray.IsMatch(member))
+ {
+ retval = this.ParseJSONArray(member);
+ }
+ else if (_rxNull.IsMatch(member))
+ {
+ retval = null;
+ }
+ else if (_rxBool.IsMatch(member))
+ {
+ retval = Boolean.Parse(member);
+ }
+ else if (_rxNumber.IsMatch(member))
+ {
+ retval = double.Parse(member);
+ }
+ else
+ {
+ //scrub the quotes.
+ member = member.Trim();
+ if (member.StartsWith("\"") && member.EndsWith("\""))
+ {
+ member = member.Remove(0, 1);
+ member = member.Substring(0, member.Length - 1);
+ }
+ retval = member;
+ }
+
+ return retval;
+ }
+
+ }
+}
View
2 NoRM/Configuration/IMongoConfigurationMap.cs
@@ -45,5 +45,7 @@ public interface IMongoConfigurationMap : IHideObjectMembers
/// Type's property alias if configured; otherwise null
/// </returns>
string GetPropertyAlias(Type type, string propertyName);
+
+ string GetTypeDescriminator(Type type);
}
}
View
6 NoRM/Configuration/ITypeConfiguration.cs
@@ -21,5 +21,11 @@ public interface ITypeConfiguration
/// The connection string.
/// </param>
void UseConnectionString(string connectionString);
+
+ /// <summary>
+ /// Marks the type as discriminator for all its subtypes.
+ /// Alternative to the MongoDiscriminatorAttribute if it is not possible or wanted to put an attribute on the types.
+ /// </summary>
+ void UseAsDiscriminator();
}
}
View
12 NoRM/Configuration/MongoConfiguration.cs
@@ -119,5 +119,17 @@ internal static string GetConnectionString(Type type)
{
return _configuration != null ? _configuration.GetConfigurationMap().GetConnectionString(type) : null;
}
+
+ /// <summary>
+ /// Given a type, get fluently configured discriminator type string
+ /// </summary>
+ /// <param retval="type">The type for whicht to get the discriminator type.</param>
+ /// <returns>
+ /// The type's discriminator type if configured; otherwise null.
+ /// </returns>
+ public static string GetTypeDiscriminator(Type type)
+ {
+ return _configuration != null ? _configuration.GetConfigurationMap().GetTypeDescriminator(type) : null;
+ }
}
}
View
38 NoRM/Configuration/MongoConfigurationMap.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using Norm.BSON;
using System.Collections.Generic;
using System.Reflection;
@@ -95,7 +96,42 @@ public string GetPropertyAlias(Type type, string propertyName)
return retval;
}
-
+ /// <summary>
+ /// Gets the fluently configured discriminator type string for a type.
+ /// </summary>
+ /// <param name="type">The type for which to get the discriminator type.</param>
+ /// <returns>The discriminator type string for the given given.</returns>
+ public string GetTypeDescriminator(Type type)
+ {
+ var inheritanceChain = GetInheritanceChain(type);
+ var discriminatedTypes = MongoTypeConfiguration.DiscriminatedTypes;
+ var discriminatingType = inheritanceChain.FirstOrDefault(t => discriminatedTypes.ContainsKey(t) && discriminatedTypes[t] == true);
+
+ if (discriminatingType != null)
+ {
+ return String.Join(",", type.AssemblyQualifiedName.Split(','), 0, 2);
+ }
+ return null;
+ }
+
+ private IEnumerable<Type> GetInheritanceChain(Type type)
+ {
+ var inheritanceChain = new List<Type> { type };
+ if (type == typeof(object))
+ {
+ return inheritanceChain;
+ }
+ inheritanceChain.AddRange(type.GetInterfaces());
+
+ while (type.BaseType != typeof(object))
+ {
+ inheritanceChain.Add(type.BaseType);
+ inheritanceChain.AddRange(type.BaseType.GetInterfaces());
+ type = type.BaseType;
+ }
+ return inheritanceChain;
+ }
+
/// <summary>
/// Gets the retval of the type's collection.
View
16 NoRM/Configuration/MongoTypeConfiguration.cs
@@ -11,7 +11,8 @@ public class MongoTypeConfiguration
private static readonly Dictionary<Type, string> _collectionNames = new Dictionary<Type, string>();
private static readonly Dictionary<Type, string> _connectionStrings = new Dictionary<Type, string>();
private static readonly Dictionary<Type, Dictionary<string, PropertyMappingExpression>> _typeConfigurations = new Dictionary<Type, Dictionary<string, PropertyMappingExpression>>();
- private static readonly IDictionary<Type, Type> _summaryTypes = new Dictionary<Type, Type>();
+ private static readonly Dictionary<Type, Type> _summaryTypes = new Dictionary<Type, Type>();
+ private static readonly Dictionary<Type, bool> _discriminatedTypes = new Dictionary<Type, bool>();
/// <summary>
/// Gets the property maps.
@@ -51,6 +52,10 @@ internal static void RemoveMappings<T>()
{
_connectionStrings.Remove(t);
}
+ if (_discriminatedTypes.ContainsKey(t))
+ {
+ _discriminatedTypes.Remove(t);
+ }
}
/// <summary>
@@ -70,5 +75,14 @@ internal static void RemoveMappings<T>()
{
get { return _collectionNames; }
}
+
+ /// <summary>
+ /// Gets the discriminated types
+ /// </summary>
+ /// <value>True if a type is marked as a discriminated type, otherwise false.</value>
+ internal static Dictionary<Type, bool> DiscriminatedTypes
+ {
+ get { return _discriminatedTypes; }
+ }
}
}
View
10 NoRM/Configuration/MongoTypeConfigurationGeneric.cs
@@ -69,5 +69,15 @@ public void UseConnectionString(string connectionString)
ConnectionStrings[typeof(T)] = connectionString;
MongoConfiguration.FireTypeChangedEvent(typeof(T));
}
+
+ /// <summary>
+ /// Marks the type as discriminator for all its subtypes.
+ /// Alternative to the MongoDiscriminatorAttribute if it is not possible or wanted to put an attribute on the types.
+ /// </summary>
+ public void UseAsDiscriminator()
+ {
+ DiscriminatedTypes[typeof (T)] = true;
+ MongoConfiguration.FireTypeChangedEvent((typeof(T)));
+ }
}
}
View
1 NoRM/NoRM.csproj
@@ -96,6 +96,7 @@
<Compile Include="BSON\Lists\ListHelper.cs" />
<Compile Include="BSON\Lists\ListWrapper.cs" />
<Compile Include="BSON\MagicProperty.cs" />
+ <Compile Include="BSON\ObjectParser.cs" />
<Compile Include="BSON\QualifierCommand.cs" />
<Compile Include="BSON\DbTypes\ObjectIdGenerator.cs" />
<Compile Include="BSON\DbTypes\ScopedCodeGeneric.cs" />

0 comments on commit 1f47199

Please sign in to comment.