Commits

Aarni Koskela committed 3e1b77b

Dropped down to .NET 2.0; more capable JSON object building, cleanup

  • Participants
  • Parent commits f42c58c

Comments (0)

Files changed (4)

File JsonParser/JsonParser.cs

 using System;
+using System.Collections;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Globalization;
 using System.IO;
+using System.Reflection;
 using System.Text;
 using System.Text.RegularExpressions;
 
 		public bool BoolValue {
 			get {
 				if (_type == JsonValueType.String) {
-					return !string.IsNullOrWhiteSpace(_strValue);
+					return !string.IsNullOrEmpty(_strValue);
 				}
 				if (_type == JsonValueType.Boolean) {
 					return _boolValue;
 
 		public static JsonValue Dictionary(Dictionary<object, object> from = null) {
 			var val = new JsonValue {_type = JsonValueType.Dict, _dictValue = new Dictionary<JsonValue, JsonValue>()};
-			if(from != null) {
+			if (from != null) {
 				foreach (var kvp in from) {
 					val._dictValue.Add(FromObject(kvp.Key), FromObject(kvp.Value));
 				}
 			return val;
 		}
 
+		// http://stackoverflow.com/a/457708/51685
+		private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
+			while (toCheck != typeof (object)) {
+				var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
+				if (generic == cur) {
+					return true;
+				}
+				toCheck = toCheck.BaseType;
+			}
+			return false;
+		}
+
 		public static JsonValue FromObject(object value) {
-			if (value == null) return Null();
-			if (value is List<object>) return List((List<object>) value);
-			if (value is Dictionary<object, object>) return Dictionary((Dictionary<object, object>) value);
-			if (value is double) return Double((double) value);
-			if (value is int) return Integer((int) value);
-			if (value is bool) return Boolean((bool) value);
+			if (value == null) {
+				return Null();
+			}
+			if (value is double) {
+				return Double((double) value);
+			}
+			if (value is int) {
+				return Integer((int) value);
+			}
+			if (value is bool) {
+				return Boolean((bool) value);
+			}
+			if (IsSubclassOfRawGeneric(typeof (List<>), value.GetType())) {
+				return List((IEnumerable<object>) value);
+			}
+			if (IsSubclassOfRawGeneric(typeof (Dictionary<,>), value.GetType())) {
+				return Dictionary(UpcastDictionary(value));
+			}
 			return String(value.ToString());
 		}
 
-		public static JsonValue List(List<object> from = null) {
+		private static Dictionary<object, object> UpcastDictionary(object value) {
+			var noArguments = new object[] {};
+			var d2 = new Dictionary<object, object>();
+			foreach (object currentKvp in (value as IEnumerable)) {
+				var kvpType = currentKvp.GetType();
+				object dKey = kvpType.InvokeMember("Key", BindingFlags.GetProperty, null, currentKvp, noArguments);
+				object dValue = kvpType.InvokeMember("Value", BindingFlags.GetProperty, null, currentKvp, noArguments);
+				if (dKey != null) {
+					d2.Add(dKey, dValue);
+				}
+			}
+			return d2;
+		}
+
+		public static JsonValue List(IEnumerable<object> from = null) {
 			var val = new JsonValue {_type = JsonValueType.List, _listValue = new List<JsonValue>()};
-			if(from != null) {
+			if (from != null) {
 				foreach (var o in from) {
 					val._listValue.Add(FromObject(o));
 				}
 		#region JSON Serializers
 
 		public string ToJSON() {
-			var sb = new StringBuilder();
-			ToJSON(sb);
-			return sb.ToString();
+			var sw = new StringWriter();
+			ToJSON(sw);
+			return sw.GetStringBuilder().ToString();
 		}
 
-		public void ToJSON(StringBuilder sb) {
+		public void ToJSON(TextWriter writer) {
 			switch (_type) {
 				case JsonValueType.Dict: {
-					sb.Append('{');
+					writer.Write('{');
 					bool first = true;
 					foreach (var kvp in _dictValue) {
 						if (!first) {
-							sb.Append(',');
+							writer.Write(',');
 						}
-						kvp.Key.ToJSON(sb);
-						sb.Append(':');
-						kvp.Value.ToJSON(sb);
+						kvp.Key.ToJSON(writer);
+						writer.Write(':');
+						kvp.Value.ToJSON(writer);
 						first = false;
 					}
-					sb.Append('}');
+					writer.Write('}');
 				}
 					break;
 
 				case JsonValueType.List: {
-					sb.Append('[');
+					writer.Write('[');
 					bool first = true;
 					foreach (var val in _listValue) {
 						if (!first) {
-							sb.Append(',');
+							writer.Write(',');
 						}
-						val.ToJSON(sb);
+						val.ToJSON(writer);
 						first = false;
 					}
-					sb.Append(']');
+					writer.Write(']');
 				}
 					break;
 				case JsonValueType.String:
-					sb.Append('"');
+					writer.Write('"');
 					foreach (var chr in _strValue) {
 						switch (chr) {
 							case '\\':
-								sb.Append("\\\\");
+								writer.Write("\\\\");
 								continue;
 							case '\n':
-								sb.Append("\\n");
+								writer.Write("\\n");
 								continue;
 							case '\r':
-								sb.Append("\\r");
+								writer.Write("\\r");
 								continue;
 						}
 						if (Char.IsControl(chr)) {
-							sb.Append("\\u");
-							sb.AppendFormat("{0:X4}", (int) chr);
+							writer.Write("\\u");
+							writer.Write(string.Format(CultureInfo.InvariantCulture, "{0:X4}", (int) chr));
 							continue;
 						}
-						sb.Append(chr);
+						writer.Write(chr);
 					}
-					sb.Append('"');
+					writer.Write('"');
 					break;
 				case JsonValueType.Integer:
-					sb.Append(_intValue.ToString(CultureInfo.InvariantCulture));
+					writer.Write(_intValue.ToString(CultureInfo.InvariantCulture));
 					break;
 				case JsonValueType.Double:
-					sb.Append(_doubleValue.ToString(CultureInfo.InvariantCulture));
+					writer.Write(_doubleValue.ToString(CultureInfo.InvariantCulture));
 					break;
 				case JsonValueType.Boolean:
-					sb.Append(_boolValue.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
+					writer.Write(_boolValue.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
 					break;
 				case JsonValueType.Null:
-					sb.Append("null");
+					writer.Write("null");
 					break;
 			}
 		}
 	}
 
 	public class JsonParser {
-		public enum ParseState {
+		private enum ParseState {
 			ListValue,
 			DictKey,
 			DictValue,
 		private static readonly Regex NumberRe = new Regex("^-?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*)?$");
 
 		private readonly TextReader reader;
-		private ParseState _state;
+		private ParseState _state = ParseState.Root;
 		private readonly Stack<JsonValue> _stack = new Stack<JsonValue>();
 		private JsonValue _root;
 
 
 		private JsonParser(TextReader reader) {
 			this.reader = reader;
-			this._state = ParseState.Root;
 		}
 
 		private JsonValue Parse() {
 				reader.Read(); // okay, read the last one then
 			}
 			var numStr = sb.ToString();
-			double d = Double.Parse(numStr, CultureInfo.InvariantCulture);
-			if (d == Math.Floor(d)) {
-				return JsonValue.Integer((int) d);
+			int intValue;
+			if (Int32.TryParse(numStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out intValue)) {
+				return JsonValue.Integer(intValue);
 			}
-			return JsonValue.Double(d);
+			return JsonValue.Double(Double.Parse(numStr, CultureInfo.InvariantCulture));
 		}
 
 		private void EnsureConstantRead(string content) {
 							sb.Append(chr);
 							break;
 						default:
-							throw new Exception("Invalid backslash escape (\\" + chr + ")");
+							throw new Exception("Invalid backslash escape (\\ + charcode " + (int) chr + ")");
 					}
 					q = false;
-					continue;
 				}
 			}
 			return JsonValue.String(sb.ToString());

File JsonParser/JsonParser.csproj

     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>JsonParser</RootNamespace>
     <AssemblyName>JsonParser</AssemblyName>
-    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
-    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+    <TargetFrameworkProfile>
+    </TargetFrameworkProfile>
     <FileAlignment>512</FileAlignment>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
     <Reference Include="System.Xml" />
   </ItemGroup>
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

File JsonParser/Program.cs

 using System;
+using System.IO;
+using System.Text;
 using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Text;
 
 namespace JsonParser
 {
-	class Program {
-		private const string testJSON = @"{
+	static class Program {
+		static void Main(string[] args) {
+			FeatureTest();
+			Console.Write("\n\n\n======\n\n\n");
+			FromObjectTest();
+			Console.Write("\n\n\n======\n\n\n");
+			TestJSONFiles();
+			Console.ReadKey();
+		}
+
+		private static void TestJSONFiles() {
+			foreach (FileInfo fi in new DirectoryInfo(".").GetFiles("*.json")) {
+				Console.Write(fi.Name.PadRight(16) + " ... ");
+				using (var file = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read)) {
+					var tr = new StreamReader(file, Encoding.UTF8);
+					
+					JsonValue obj;
+					try {
+						obj = JsonParser.Parse(tr);
+					} catch(Exception exc) {
+						if(!fi.Name.Contains("fail")) {
+							Console.WriteLine("Shouldn't have failed at this file.");
+							throw;
+						}
+						Console.WriteLine("  OK, failed with " + exc.Message);
+						continue;
+					}
+					Console.WriteLine("  OK, success, regenerated JSON has {0} chars.", obj.ToJSON().Length);
+				}
+			}
+		}
+
+		private static void FeatureTest() {
+			var root = JsonParser.Parse(@"{
     ""glossary"": {
         ""title"": ""example glossary"",
 		""GlossDiv"": {
-			""awesomeness"": 3.141,
-			""uberawesome"": -1024,
+			""double"": 3.141,
+			""integer"": -1024,
 			""deleted"": true,
-            ""title"": ""\\\\S\u3F3C\b\r\n"",
+            ""title"": ""\\\\S\u3F3C"",
 			""GlossList"": {
                 ""GlossEntry"": {
                     ""ID"": ""SGML"",
             }
         }
     }
-}";
+}");
+			JsonParser.Parse(root.ToJSON()); // reparseable?
+			Console.WriteLine(".glossary = {0}", root.Get("glossary"));
+			var div = root.ResolvePath("glossary", "GlossDiv");
+			Console.WriteLine("integer = {0}", div.Get("integer").IntValue);
+			Console.WriteLine("double = {0}", div.Get("double").DoubleValue);
+			Console.WriteLine("count = {0}", div.Count);
+			Console.WriteLine("stringstringdict:");
+			foreach(var kvp in div.GetStringStringDict()) {
+				Console.WriteLine("  {0} : {1}", kvp.Key.PadRight(30), kvp.Value);
+			}
+			var entry = div.ResolvePath("GlossList", "GlossEntry");
+			var seeAlso = entry.ResolvePath("GlossDef", "GlossSeeAlso");
+			Console.WriteLine("SeeAlso first: {0}", seeAlso.Get(0).StrValue);
+			Console.WriteLine("SeeAlso first by full path: {0}", root.ResolvePath("glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso.0").StrValue);
+			Console.WriteLine("glossee: {0}", entry.ResolvePath("GlossSee").StrValue);
+			Console.WriteLine("Deleted: {0}", root.ResolvePath("glossary.GlossDiv.deleted").BoolValue);
 
-
-		static void Main(string[] args) {
-			FeatureTest();
-			Console.Write("\n\n\n======\n\n\n");
-			TestJSONFiles();
-			Console.ReadKey();
-		}
-
-		private static void TestJSONFiles() {
-			foreach (FileInfo fi in new DirectoryInfo(".").GetFiles("*.json")) {
-				Console.WriteLine(fi.Name + " ...");
-				using (var file = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read)) {
-					var tr = new StreamReader(file, Encoding.UTF8);
-					
-					JsonValue obj = null;
-					try {
-						obj = JsonParser.Parse(tr);
-					} catch(Exception exc) {
-						if(!fi.Name.Contains("fail")) {
-							Console.WriteLine("Shouldn't have failed at this file.");
-							throw;
-						}
-						Console.WriteLine("  OK, failed with " + exc.Message);
-						continue;
-					}
-					Console.WriteLine("  OK, success.");
-				}
+			using(var ms = new MemoryStream()) {
+				var sw = new StreamWriter(ms, Encoding.UTF32);
+				root.ToJSON(sw);
+				sw.Flush();
+				Console.WriteLine("Writing API - JSON UTF-32 bytes: {0}", ms.Position);
 			}
 		}
 
-		private static void FeatureTest() {
-			var el = JsonParser.Parse(testJSON);
-			var json = el.ToJSON();
-			Console.WriteLine(el.ToString());
-			Console.WriteLine(json);
-			var el2 = JsonParser.Parse(json);
-			var ent = el.ResolvePath("glossary", "GlossDiv", "GlossList", "GlossEntry");
-			var seeAlso = ent.ResolvePath("GlossDef", "GlossSeeAlso");
-			Console.WriteLine("SeeAlso first: {0}", seeAlso.Get(0).StrValue);
-			Console.WriteLine("SeeAlso2 first: {0}", el.ResolvePath("glossary.GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso.0").StrValue);
-			Console.WriteLine("glossee: {0}", ent.ResolvePath("GlossSee").StrValue);
-			Console.WriteLine("Deleted: {0}", el.ResolvePath("glossary.GlossDiv.deleted").BoolValue);
+		private static void FromObjectTest() {
+			var jv = JsonValue.FromObject(new List<object> {
+				"foo",
+				3.141,
+				-1024,
+				new Dictionary<string, bool> {
+					{"hello", true},
+					{"yes", false},
+				}
+			});
+			Console.Write(jv.ToJSON());
 		}
 	}
 }

File JsonParser/Properties/AssemblyInfo.cs

 using System.Reflection;
-using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 // General Information about an assembly is controlled through the following