123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- // JsonKit v0.5 - A simple but flexible Json library in a single .cs file.
- //
- // Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product
- // except in compliance with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software distributed under the
- // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- // either express or implied. See the License for the specific language governing permissions
- // and limitations under the License.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
- using System.Runtime.Serialization;
- namespace Topten.JsonKit
- {
- // Stores reflection info about a type
- class ReflectionInfo
- {
- // List of members to be serialized
- public List<JsonMemberInfo> Members;
- // Cache of these ReflectionInfos's
- static ThreadSafeCache<Type, ReflectionInfo> _cache = new ThreadSafeCache<Type, ReflectionInfo>();
- public static MethodInfo FindFormatJson(Type type)
- {
- if (type.IsValueType)
- {
- // Try `void FormatJson(IJsonWriter)`
- var formatJson = type.GetMethod("FormatJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(IJsonWriter) }, null);
- if (formatJson != null && formatJson.ReturnType == typeof(void))
- return formatJson;
- // Try `string FormatJson()`
- formatJson = type.GetMethod("FormatJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
- if (formatJson != null && formatJson.ReturnType == typeof(string))
- return formatJson;
- }
- return null;
- }
- public static MethodInfo FindParseJson(Type type)
- {
- // Try `T ParseJson(IJsonReader)`
- var parseJson = type.GetMethod("ParseJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(IJsonReader) }, null);
- if (parseJson != null && parseJson.ReturnType == type)
- return parseJson;
- // Try `T ParseJson(string)`
- parseJson = type.GetMethod("ParseJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null);
- if (parseJson != null && parseJson.ReturnType == type)
- return parseJson;
- return null;
- }
- // Write one of these types
- public void Write(IJsonWriter w, object val)
- {
- w.WriteDictionary(() =>
- {
- var writing = val as IJsonWriting;
- if (writing != null)
- writing.OnJsonWriting(w);
- foreach (var jmi in Members.Where(x=>!x.Deprecated))
- {
- // Exclude null?
- var mval = jmi.GetValue(val);
- if (jmi.ExcludeIfNull && mval == null)
- continue;
- if (jmi.ExcludeIfEmpty)
- {
- if (mval == null)
- continue;
- if (mval is System.Collections.IEnumerable e && !e.GetEnumerator().MoveNext())
- continue;
- }
- if (jmi.ExcludeIfEquals != null)
- {
- if (jmi.ExcludeIfEquals.Equals(mval))
- continue;
- }
- w.WriteKeyNoEscaping(jmi.JsonKey);
- w.WriteValue(mval);
- }
- var written = val as IJsonWritten;
- if (written != null)
- written.OnJsonWritten(w);
- });
- }
- // Read one of these types.
- // NB: Although JsonKit.JsonParseInto only works on reference type, when using reflection
- // it also works for value types so we use the one method for both
- public void ParseInto(IJsonReader r, object into)
- {
- try
- {
- var loading = into as IJsonLoading;
- if (loading != null)
- loading.OnJsonLoading(r);
- r.ParseDictionary(key =>
- {
- ParseFieldOrProperty(r, into, key);
- });
- var loaded = into as IJsonLoaded;
- if (loaded != null)
- loaded.OnJsonLoaded(r);
- }
- catch (Exception x)
- {
- var loadex = into as IJsonLoadException;
- if (loadex != null)
- loadex.OnJsonLoadException(r, x);
- }
- }
- // The member info is stored in a list (as opposed to a dictionary) so that
- // the json is written in the same order as the fields/properties are defined
- // On loading, we assume the fields will be in the same order, but need to
- // handle if they're not. This function performs a linear search, but
- // starts after the last found item as an optimization that should work
- // most of the time.
- int _lastFoundIndex = 0;
- bool FindMemberInfo(string name, out JsonMemberInfo found)
- {
- for (int i = 0; i < Members.Count; i++)
- {
- int index = (i + _lastFoundIndex) % Members.Count;
- var jmi = Members[index];
- if (jmi.JsonKey == name)
- {
- _lastFoundIndex = index;
- found = jmi;
- return true;
- }
- }
- found = null;
- return false;
- }
- // Parse a value from IJsonReader into an object instance
- public void ParseFieldOrProperty(IJsonReader r, object into, string key)
- {
- // IJsonLoadField
- var lf = into as IJsonLoadField;
- if (lf != null && lf.OnJsonField(r, key))
- return;
- // Find member
- JsonMemberInfo jmi;
- if (FindMemberInfo(key, out jmi))
- {
- // Try to keep existing instance
- if (jmi.KeepInstance)
- {
- var subInto = jmi.GetValue(into);
- if (subInto != null)
- {
- r.ParseInto(subInto);
- return;
- }
- }
- // Parse and set
- var val = r.Parse(jmi.MemberType);
- jmi.SetValue(into, val);
- return;
- }
- }
- // Get the reflection info for a specified type
- public static ReflectionInfo GetReflectionInfo(Type type)
- {
- // Check cache
- return _cache.Get(type, () =>
- {
- var allMembers = Utils.GetAllFieldsAndProperties(type);
- // Does type have a [Json] attribute
- var typeAttr = type.GetCustomAttributes(typeof(JsonAttribute), true).OfType<JsonAttribute>().FirstOrDefault();
- bool typeMarked = typeAttr != null;
- // Do any members have a [Json] attribute
- bool anyFieldsMarked = allMembers.Any(x => x.GetCustomAttributes(typeof(JsonAttribute), false).OfType<JsonAttribute>().Any());
- // If the type is marked with [Json(ExplicitFieldsOnly = true)] then ignore the type attribute
- // and only serialize fields explicitly marked.
- if (typeAttr != null && typeAttr.ExplicitMembersOnly)
- {
- anyFieldsMarked = true;
- typeAttr = null;
- typeMarked = false;
- }
- // Try with DataContract and friends
- if (!typeMarked && !anyFieldsMarked && type.GetCustomAttributes(typeof(DataContractAttribute), true).OfType<DataContractAttribute>().Any())
- {
- var ri = CreateReflectionInfo(type, mi =>
- {
- // Get attributes
- var attr = mi.GetCustomAttributes(typeof(DataMemberAttribute), false).OfType<DataMemberAttribute>().FirstOrDefault();
- if (attr != null)
- {
- return new JsonMemberInfo()
- {
- Member = mi,
- JsonKey = attr.Name ?? mi.Name, // No lower case first letter if using DataContract/Member
- };
- }
- return null;
- });
- ri.Members.Sort((a, b) => String.CompareOrdinal(a.JsonKey, b.JsonKey)); // Match DataContractJsonSerializer
- return ri;
- }
- {
- // Should we serialize all public methods?
- bool serializeAllPublics = typeMarked || !anyFieldsMarked;
- // Build
- var ri = CreateReflectionInfo(type, mi =>
- {
- // Explicitly excluded?
- if (mi.GetCustomAttributes(typeof(JsonExcludeAttribute), false).Any())
- return null;
- // Get attributes
- var attr = mi.GetCustomAttributes(typeof(JsonAttribute), false).OfType<JsonAttribute>().FirstOrDefault();
- if (attr != null)
- {
- return new JsonMemberInfo()
- {
- Member = mi,
- JsonKey = attr.Key ?? mi.Name.Substring(0, 1).ToLower() + mi.Name.Substring(1),
- Attribute = attr,
- };
- }
- // Serialize all publics?
- if (serializeAllPublics && Utils.IsPublic(mi))
- {
- return new JsonMemberInfo()
- {
- Member = mi,
- JsonKey = mi.Name.Substring(0, 1).ToLower() + mi.Name.Substring(1),
- };
- }
- return null;
- });
- return ri;
- }
- });
- }
- public static ReflectionInfo CreateReflectionInfo(Type type, Func<MemberInfo, JsonMemberInfo> callback)
- {
- // Work out properties and fields
- var members = Utils.GetAllFieldsAndProperties(type).Select(x => callback(x)).Where(x => x != null).ToList();
- // Anything with KeepInstance must be a reference type
- var invalid = members.FirstOrDefault(x => x.KeepInstance && x.MemberType.IsValueType);
- if (invalid!=null)
- {
- throw new InvalidOperationException(string.Format("KeepInstance=true can only be applied to reference types ({0}.{1})", type.FullName, invalid.Member));
- }
- // Must have some members
- /*
- if (!members.Any() && !Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
- return null;
- */
- // Create reflection info
- return new ReflectionInfo() { Members = members };
- }
- }
- }
|