// 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 Members; // Cache of these ReflectionInfos's static ThreadSafeCache _cache = new ThreadSafeCache(); 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().FirstOrDefault(); bool typeMarked = typeAttr != null; // Do any members have a [Json] attribute bool anyFieldsMarked = allMembers.Any(x => x.GetCustomAttributes(typeof(JsonAttribute), false).OfType().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().Any()) { var ri = CreateReflectionInfo(type, mi => { // Get attributes var attr = mi.GetCustomAttributes(typeof(DataMemberAttribute), false).OfType().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().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 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 }; } } }