diff --git a/UnitTests/Tests.cs b/UnitTests/Tests.cs index b9bdb30..e4317bb 100644 --- a/UnitTests/Tests.cs +++ b/UnitTests/Tests.cs @@ -1386,6 +1386,85 @@ public static void CircularReferences() Assert.AreEqual(p.o2obj, p.child.child); } + + class Circular + { + public Guid Id; + public Circular Parent; + public Circular Child; + + public Circular() + { + Id = Guid.NewGuid(); + } + + public static Circular Create() + { + var a = new Circular(); + var b = new Circular() { Parent = a }; + a.Child = b; + var c = new Circular() { Parent = b }; + b.Child = c; + var d = new Circular() { Parent = c }; + c.Child = d; + return a; + } + + } + + [Test] + public static void CircularReferencesThrows() + { + var obj = Circular.Create(); + + Assert.That(() => + { + JSON.ToJSON(obj, + new JSONParameters() + { + SerializerMaxDepth = 2, + SerializerMaxDepthHandling = InvalidObjectActions.Throw, + InlineCircularReferences = true + }); + }, + Throws.TypeOf()); + } + + [Test] + public static void CircularReferencesNull() + { + var obj = Circular.Create(); + + var s = JSON.ToJSON(obj, + new JSONParameters() + { + SerializeNullValues = false, + SerializerMaxDepth = 3, + SerializerMaxDepthHandling = InvalidObjectActions.InsertNull, + InlineCircularReferences = true + }); + Console.WriteLine(JSON.Beautify(s)); + StringAssert.Contains("null", s); + } + + [Test] + public static void CircularReferencesEmpty() + { + var obj = Circular.Create(); + + var s = JSON.ToJSON(obj, + new JSONParameters() + { + SerializeNullValues = false, + SerializerMaxDepth = 4, + SerializerMaxDepthHandling = InvalidObjectActions.InsertEmpty, + InlineCircularReferences = true + }); + Console.WriteLine(s); + Console.WriteLine(JSON.Beautify(s)); + StringAssert.Contains("{}", s); + } + public class lol { public List> r; @@ -1625,7 +1704,7 @@ public static void lowercaseSerilaize() r.Field1 = "dsasdF"; r.Field2 = 2312; r.date = DateTime.Now; - var s = JSON.ToNiceJSON(r, new JSONParameters { SerializeToLowerCaseNames = true }); + var s = JSON.ToNiceJSON(r, new JSONParameters { NamingStyle = NamingStyles.LowerCase }); Console.WriteLine(s); var o = JSON.ToObject(s); Assert.IsNotNull(o); @@ -1633,6 +1712,32 @@ public static void lowercaseSerilaize() Assert.AreEqual(2312, (o as Retclass).Field2); } + [Test] + public static void PropertyName_Normal() + { + var obj = new { NameProperty = "Test" }; + var s = JSON.ToJSON(obj, new JSONParameters { NamingStyle = NamingStyles.Normal, EnableAnonymousTypes = true }); + Console.WriteLine(s); + Assert.AreEqual("{\"NameProperty\":\"Test\"}", s); + } + + [Test] + public static void PropertyName_LowerCase() + { + var obj = new { NameProperty = "Test" }; + var s = JSON.ToJSON(obj, new JSONParameters { NamingStyle = NamingStyles.LowerCase, EnableAnonymousTypes = true }); + Console.WriteLine(s); + Assert.AreEqual("{\"nameproperty\":\"Test\"}", s); + } + + [Test] + public static void PropertyName_CamelCase() + { + var obj = new { NameProperty = "Test", CRC = "1234" }; + var s = JSON.ToJSON(obj, new JSONParameters { NamingStyle = NamingStyles.CamelCase, EnableAnonymousTypes = true }); + Console.WriteLine(s); + Assert.AreEqual("{\"nameProperty\":\"Test\",\"crc\":\"1234\"}", s); + } public class nulltest { diff --git a/fastJSON/JSON.cs b/fastJSON/JSON.cs index 06aa486..520e7bc 100644 --- a/fastJSON/JSON.cs +++ b/fastJSON/JSON.cs @@ -12,6 +12,40 @@ namespace fastJSON { public delegate string Serialize(object data); public delegate object Deserialize(string data); + public delegate string TextHandler(string name); + public delegate void InvalidObjectHandler(object obj); + + public enum NamingStyles + { + /// + /// Leave name as is + /// + Normal = 0, + /// + /// Transform to lower case + /// + LowerCase = 1, + /// + /// Transform to camel case (Lowers the first char) + /// + CamelCase = 2 + } + + public enum InvalidObjectActions + { + /// + /// Throws an exception + /// + Throw = 0, + /// + /// Inserts a "null" + /// + InsertNull = 1, + /// + /// Inserts an empty object "{}" + /// + InsertEmpty = 2 + } public sealed class JSONParameters { @@ -83,14 +117,23 @@ public sealed class JSONParameters /// public byte SerializerMaxDepth = 20; /// + /// Default action for when the limit is hit. + /// + public InvalidObjectActions SerializerMaxDepthHandling = InvalidObjectActions.Throw; + /// /// Inline circular or already seen objects instead of replacement with $i (default = False) /// public bool InlineCircularReferences = false; /// /// Save property/field names as lowercase (default = false) /// + [Obsolete("Use the NamingStyle property instead")] public bool SerializeToLowerCaseNames = false; /// + /// Naming style for property/field name (default = normal, leave as is) + /// + public NamingStyles NamingStyle = NamingStyles.Normal; + /// /// Formatter indent spaces (default = 3) /// public byte FormatterIndentSpaces = 3; @@ -1208,4 +1251,48 @@ DataTable CreateDataTable(Dictionary reader, Dictionary s.ToLower(); + break; + case NamingStyles.CamelCase: + _handle = (s) => + { + var chars = s.ToCharArray(); + var isAllUpper = true; + for (var ci = 0; ci < chars.Length; ci++) + { + if (!Char.IsUpper(chars[ci])) + { + isAllUpper = false; + break; + } + } + if (isAllUpper) + return s.ToLower(); + chars[0] = Char.ToLower(chars[0]); + return new string(chars); + }; + break; + case NamingStyles.Normal: + default: + _handle = (s) => s; + break; + } + } + + internal string Handle(string name) + { + return _handle(name); + } + + } + } \ No newline at end of file diff --git a/fastJSON/JsonSerializer.cs b/fastJSON/JsonSerializer.cs index 8aaebf8..3ca430b 100644 --- a/fastJSON/JsonSerializer.cs +++ b/fastJSON/JsonSerializer.cs @@ -8,6 +8,7 @@ using System.IO; using System.Text; using System.Collections.Specialized; +using System.Runtime.Remoting.Messaging; namespace fastJSON { @@ -22,12 +23,34 @@ internal sealed class JSONSerializer private Dictionary _cirobj = new Dictionary(); private JSONParameters _params; private bool _useEscapedUnicode = false; + private List _skipObjects = new List(); + private readonly nameHandler _nameHandler; + private InvalidObjectHandler _handleInvalidObject; internal JSONSerializer(JSONParameters param) { _params = param; _useEscapedUnicode = _params.UseEscapedUnicode; _MAX_DEPTH = _params.SerializerMaxDepth; + _nameHandler = new nameHandler(param.NamingStyle); + assignInvalidObjectHandler(); + } + + private void assignInvalidObjectHandler() + { + switch (_params.SerializerMaxDepthHandling) + { + case InvalidObjectActions.InsertEmpty: + _handleInvalidObject = (obj) => { _output.Append("{}"); }; + break; + case InvalidObjectActions.InsertNull: + _handleInvalidObject = (obj) => { _output.Append("null"); }; + break; + case InvalidObjectActions.Throw: + default: + _handleInvalidObject = (obj) => { throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH); }; + break; + } } internal string ConvertToJSON(object obj) @@ -55,8 +78,15 @@ internal string ConvertToJSON(object obj) return _output.ToString(); } - private void WriteValue(object obj) + private bool WriteValue(object obj) { + // Check for object reference limitation + if (obj != null && _skipObjects.Contains(obj)) + { + _handleInvalidObject(obj); + return false; + } + if (obj == null || obj is DBNull) _output.Append("null"); @@ -124,7 +154,7 @@ obj is uint || obj is ulong #endif else if (_params.KVStyleStringDictionary == false && obj is IDictionary && - obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string)) + obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string)) WriteStringDictionary((IDictionary)obj); else if (obj is IDictionary) @@ -156,12 +186,14 @@ obj is uint || obj is ulong else WriteObject(obj); + + return true; } private void WriteDateTimeOffset(DateTimeOffset d) { DateTime dt = _params.UseUTCDateTime ? d.UtcDateTime : d.DateTime; - + write_date_value(dt); var ticks = dt.Ticks % TimeSpan.TicksPerSecond; @@ -198,10 +230,7 @@ private void WriteNV(NameValueCollection nameValueCollection) else { if (pendingSeparator) _output.Append(','); - if (_params.SerializeToLowerCaseNames) - WritePair(key.ToLower(), nameValueCollection[key]); - else - WritePair(key, nameValueCollection[key]); + WritePair(_nameHandler.Handle(key), nameValueCollection[key]); pendingSeparator = true; } } @@ -224,10 +253,7 @@ private void WriteSD(StringDictionary stringDictionary) if (pendingSeparator) _output.Append(','); string k = (string)entry.Key; - if (_params.SerializeToLowerCaseNames) - WritePair(k.ToLower(), entry.Value); - else - WritePair(k, entry.Value); + WritePair(_nameHandler.Handle(k), entry.Value); pendingSeparator = true; } } @@ -433,6 +459,15 @@ private void WriteObject(object obj) return; } } + + // Ban the object for further serialization + if (_current_depth >= _MAX_DEPTH) + { + _handleInvalidObject(obj); + _skipObjects.Add(obj); + return; + } + if (_params.UsingGlobalTypes == false) _output.Append('{'); else @@ -448,9 +483,6 @@ private void WriteObject(object obj) } _TypesWritten = true; _current_depth++; - if (_current_depth > _MAX_DEPTH) - throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH); - Dictionary map = new Dictionary(); Type t = obj.GetType(); @@ -489,8 +521,10 @@ private void WriteObject(object obj) _output.Append(','); if (p.memberName != null) WritePair(p.memberName, o); - else if (_params.SerializeToLowerCaseNames) + else if (_params.NamingStyle == NamingStyles.LowerCase) WritePair(p.lcName, o); + else if (_params.NamingStyle == NamingStyles.CamelCase) + WritePair(p.ccName, o); else WritePair(p.Name, o); if (o != null && _params.UseExtensions) @@ -533,16 +567,32 @@ private void WriteArray(IEnumerable array) { _output.Append('['); + // Get the index before any additions, in case of a recovery is needed + var recoveryLength = _output.Length; + var needsRecovery = false; + bool pendingSeperator = false; foreach (object obj in array) { if (pendingSeperator) _output.Append(','); - WriteValue(obj); + if (!WriteValue(obj)) + { + // Object did not serialize (Probably hit the circular reference limit) + needsRecovery = true; + break; + } pendingSeperator = true; } + + // Serialization error, rollback the entire array (Better empty than incomplete data) + if (needsRecovery) + { + var toRemove = _output.Length - recoveryLength; + _output.Remove(recoveryLength, toRemove); + } _output.Append(']'); } @@ -562,10 +612,7 @@ private void WriteStringDictionary(IDictionary dic) if (pendingSeparator) _output.Append(','); string k = (string)entry.Key; - if (_params.SerializeToLowerCaseNames) - WritePair(k.ToLower(), entry.Value); - else - WritePair(k, entry.Value); + WritePair(_nameHandler.Handle(k), entry.Value); pendingSeparator = true; } } @@ -584,12 +631,9 @@ private void WriteStringDictionary(IEnumerable> dic else { if (pendingSeparator) _output.Append(','); - string k = entry.Key; - if (_params.SerializeToLowerCaseNames) - WritePair(k.ToLower(), entry.Value); - else - WritePair(k, entry.Value); + string k = entry.Key; + WritePair(_nameHandler.Handle(k), entry.Value); pendingSeparator = true; } } @@ -645,7 +689,7 @@ private void WriteString(string s) } else { - if (c != '\t' && c != '\n' && c != '\r' && c != '\"' && c != '\\' && c!='\0')// && c != ':' && c!=',') + if (c != '\t' && c != '\n' && c != '\r' && c != '\"' && c != '\\' && c != '\0')// && c != ':' && c!=',') { if (runIndex == -1) runIndex = index; diff --git a/fastJSON/Reflection.cs b/fastJSON/Reflection.cs index 42aac40..270d06d 100644 --- a/fastJSON/Reflection.cs +++ b/fastJSON/Reflection.cs @@ -16,6 +16,7 @@ internal struct Getters { public string Name; public string lcName; + public string ccName; public string memberName; public Reflection.GenericGetter Getter; } @@ -92,6 +93,7 @@ private Reflection() private SafeDictionary> _propertycache = new SafeDictionary>(); private SafeDictionary _genericTypes = new SafeDictionary(); private SafeDictionary _genericTypeDef = new SafeDictionary(); + private readonly nameHandler _ccNameHandler = new nameHandler(NamingStyles.CamelCase); #region bjson custom types internal UnicodeEncoding unicode = new UnicodeEncoding(); @@ -592,7 +594,7 @@ internal Getters[] GetGetters(Type type, bool ShowReadOnlyProperties, List #endif GenericGetter g = CreateGetMethod(type, p); if (g != null) - getters.Add(new Getters { Getter = g, Name = p.Name, lcName = p.Name.ToLower(), memberName = mName }); + getters.Add(new Getters { Getter = g, Name = p.Name, lcName = p.Name.ToLower(), ccName = _ccNameHandler.Handle(p.Name), memberName = mName }); } FieldInfo[] fi = type.GetFields(bf); @@ -631,7 +633,7 @@ internal Getters[] GetGetters(Type type, bool ShowReadOnlyProperties, List { GenericGetter g = CreateGetField(type, f); if (g != null) - getters.Add(new Getters { Getter = g, Name = f.Name, lcName = f.Name.ToLower(), memberName = mName }); + getters.Add(new Getters { Getter = g, Name = f.Name, lcName = f.Name.ToLower(), ccName = _ccNameHandler.Handle(f.Name), memberName = mName }); } } val = getters.ToArray();