JavaScriptSerializer - custom property name

前端 未结 2 1643
故里飘歌
故里飘歌 2020-12-03 22:28

I am using JavaScriptSerializer to deserialize json data. Everything works pretty well, but my problem is, that one property in json data is named \'base\', so I cannot cre

2条回答
  •  暖寄归人
    2020-12-03 23:00

    As dbc wrote, JavaScriptSerializer is very limited. Since we cannot use Json.NET in our project, I wrote a JavaScriptConverter called CustomJavaScriptSerializer to enhance JavaScriptSerializer.

    Unfortunately, because of the way JavaScriptConverter and JavaScriptSerializer work together (that could have been done better, Microsoft!), it is necessary that you derive your class to be serialized from CustomJavaScriptSerializer. That's the only limitation.

    But then you have full control and flexibility over how your class is serialized/deserialized. Some handy features are already bulit in, like partial support for JsonProperty or lower-casing the first letter of all property names (as it is convention in JavaScript). For exact usage and all features, see comments in the code. In addition to this, you can override serialization methods in any of your derived classes to fine-tune serialization specific to that particular class.

    Though I think the class is very reliable, of course, as always, I do not take over any liability of any kind. Use on your own risk.

    That said, here's the code:

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Reflection;
    using System.Web.Script.Serialization;
    
    namespace SomeNamespace
    {
        #region Class CustomJavaScriptSerializer
    
        /// 
        /// Custom JavaScript serializer, see https://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptconverter%28v=vs.110%29.aspx
        /// 
        /// 
        /// Used to enhance functionality of standard . 
        /// E.g. to support  that provides some properties known from Newtonsoft's JavaScript serializer, 
        /// see https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonPropertyAttribute.htm.
        /// Additionally, there is an attribute  that can be applied to the class 
        /// to manipulate JSON serialization behavior of all properties of the class.
        /// Use this JSON serializer when including Newtonsoft's JavaScript serializer is not an option for you.
        /// 
        /// Usage: 
        /// 
        ///  - Just derive your class to be JSON serialized / deserialized from this class. 
        ///  - You now can decorate the properties of your class with e.g. [JsonProperty( "someName" )]. See  for details.
        ///  - Additionally, you can decorate your class with e.g. [JsonClass( DoNotLowerCaseFirstLetter = true)]. See  for details.
        ///  - Important! Use static methods  and  of this class for JSON serialization / deserialization.
        ///  - For further customization specific to your class, you can override  and  in your derived class.
        ///   
        /// Example:
        /// 
        /// 
        ///     /// The tooltip. Will be transferred to JavaScript as "tooltext".
        ///     /// 
        ///     [JsonProperty( "tooltext" )]
        ///     public string Tooltip
        ///     {
        ///         get;
        ///         set;
        ///     }
        ///     
        ///     ...
        /// }
        /// 
        /// ]]>
        /// 
        public abstract class CustomJavaScriptSerializer : JavaScriptConverter
        {
            #region Fields
    
            /// 
            /// Dictionary to collect all derived  to be registered with 
            /// 
            private static Dictionary convertersToRegister = new Dictionary();
    
            /// 
            /// Sync for .
            /// 
            private static readonly object sync = new object();
    
            #endregion
    
            #region Properties
    
            /// 
            /// All derived  to be registered with 
            /// 
            public static IEnumerable ConvertersToRegister
            {
                get
                {
                    return CustomJavaScriptSerializer.convertersToRegister.Values;
                }
            }
    
            /// 
            ///  retrieved from decoration of the derived class.
            /// 
            /// 
            /// Is only set when an instance of this class is used for serialization. See constructor for details.
            /// 
            protected JsonClassAttribute ClassAttribute
            {
                get;
                private set;
            }
    
            #endregion
    
            #region Constructors
    
            /// 
            /// Default constructor
            /// 
            public CustomJavaScriptSerializer()
            {
                Type type = this.GetType();
    
                if ( CustomJavaScriptSerializer.convertersToRegister.ContainsKey( type ) )
                    return;
    
                lock( sync )
                {
                    // Remember this CustomJavaScriptSerializer instance to do serialization for this type.
                    if ( CustomJavaScriptSerializer.convertersToRegister.ContainsKey( type ) )
                        return;
    
                    // Performance: Set ClassAttribute only for the instance used for serialization.
                    this.ClassAttribute = ( this.GetType().GetCustomAttributes( typeof( JsonClassAttribute ), true ).FirstOrDefault() as JsonClassAttribute ) ?? new JsonClassAttribute();
                    convertersToRegister[ type ] = this;
                }
            }
    
            #endregion
    
            #region Public Methods
    
            /// 
            /// Converts  to a JSON string.
            /// 
            /// The object to serialize.
            /// The serialized JSON string.
            public static string JsonSerialize( object obj )
            {
                var serializer = new JavaScriptSerializer();
                serializer.RegisterConverters( CustomJavaScriptSerializer.ConvertersToRegister );
                serializer.MaxJsonLength = int.MaxValue;
                return serializer.Serialize( obj );
            }
    
            /// 
            /// Converts a JSON-formatted string to an object of the specified type.
            /// 
            /// The JSON string to deserialize.
            /// The type of the resulting object.
            /// The deserialized object.
            public static object JsonDeserialize( string input, Type targetType )
            {
                var serializer = new JavaScriptSerializer();
                serializer.RegisterConverters( CustomJavaScriptSerializer.ConvertersToRegister );
                serializer.MaxJsonLength = int.MaxValue;
                return serializer.Deserialize( input, targetType );
            }
    
            /// 
            /// Converts the specified JSON string to an object of type .
            /// 
            /// The type of the resulting object.
            /// The JSON string to be deserialized.
            /// The deserialized object.
            public static T JsonDeserialize( string input )
            {
                return (T)CustomJavaScriptSerializer.JsonDeserialize( input, typeof( T ) );
            }
    
            /// 
            /// Get this object serialized as JSON string.
            /// 
            /// This object as JSON string.
            public string ToJson()
            {
                return CustomJavaScriptSerializer.JsonSerialize( this );
            }
    
            #endregion
    
            #region Overrides
    
            /// 
            /// Return list of supported types. This is just a derived class here.
            /// 
            [ScriptIgnore]
            public override IEnumerable SupportedTypes
            {
                get
                {
                    return new ReadOnlyCollection( new List(){ this.GetType() } );
                }
            }
    
            /// 
            /// Serialize the passed .
            /// 
            /// The object to serialize.
            /// The  calling this method.
            /// A dictionary with name value pairs representing property name value pairs as they shall appear in JSON. 
            public override IDictionary Serialize( object obj, JavaScriptSerializer serializer )
            {
                var result = new Dictionary();
    
                if ( obj == null )
                    return result;
    
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
    
                PropertyInfo[] properties = this.GetType().GetProperties( bindingFlags );
    
                foreach ( PropertyInfo property in properties )
                {
                    KeyValuePair kvp = this.GetSerializedProperty( obj, property );
                    if ( !string.IsNullOrEmpty( kvp.Key ) )
                        result[ kvp.Key ] = kvp.Value;
                }
    
                return result;
            }
    
            /// 
            /// Deserialize  to .
            /// 
            /// 
            /// Reverse method to 
            /// 
            /// The dictionary to be deserialized.
            /// Type to deserialize to. This is the type of the derived class, see .
            /// The  calling this method.
            /// An object of type  with property values set from .
            public override object Deserialize( IDictionary dictionary, Type type, JavaScriptSerializer serializer )
            {
                if ( dictionary == null )
                    throw new ArgumentNullException( "dictionary" );
    
                if ( type == null )
                    throw new ArgumentNullException( "type" );
    
                if ( serializer == null )
                    throw new ArgumentNullException( "serializer" );
    
                // This will fail if type has no default constructor.
                object result = Activator.CreateInstance( type );
    
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
    
                PropertyInfo[] properties = this.GetType().GetProperties( bindingFlags );
    
                foreach ( PropertyInfo property in properties )
                {
                    this.SetDerializedProperty( result, property, dictionary, serializer );
                }
    
                return result;
            }
    
            #endregion
    
            #region Protected Methods
    
            /// 
            /// Get a key value pair as base for serialization, based on the passed .
            /// 
            /// 
            /// The returned  key represents the property's name in JSON, the value its value.
            /// 
            /// The object to serialize.
            /// The To be serialized.
            /// The requested key value pair or an empty key value pair (key = null) to ignore this property.
            protected virtual KeyValuePair GetSerializedProperty( object obj, PropertyInfo property )
            {
                var result = new KeyValuePair();
    
                if ( property == null || !property.CanRead )
                    return result;
    
                object value = property.GetValue( obj );
                if ( value == null && !this.ClassAttribute.SerializeNullValues )
                    return result;
    
                JsonPropertyAttribute jsonPropertyAttribute = this.GetJsonPropertyAttribute( property );
                if ( jsonPropertyAttribute == null || jsonPropertyAttribute.Ignored )
                    return result;
    
                if ( value != null && jsonPropertyAttribute.UseToString )
                    value = value.ToString();
    
                string name = jsonPropertyAttribute.PropertyName;
                return new KeyValuePair( name, value );
            }
    
            /// 
            /// Set  of  with value provided in .
            /// 
            /// The object to set .
            /// The property to set its value.
            /// Dictionary with property name - value pairs to query for  value.
            /// The  calling this method.
            public virtual void SetDerializedProperty( object obj, PropertyInfo property, IDictionary dictionary, JavaScriptSerializer serializer )
            {
                if ( obj == null || property == null || !property.CanWrite || dictionary == null || serializer == null )
                    return;
    
                JsonPropertyAttribute jsonPropertyAttribute = this.GetJsonPropertyAttribute( property );
                if ( jsonPropertyAttribute == null || jsonPropertyAttribute.Ignored || jsonPropertyAttribute.UseToString )
                    return;
    
                string name = jsonPropertyAttribute.PropertyName;
                if ( !dictionary.ContainsKey( name ) )
                    return;
    
                object value = dictionary[ name ];
    
                // Important! Use JavaScriptSerializer.ConvertToType so that CustomJavaScriptSerializers of properties of this class are called recursively. 
                object convertedValue = serializer.ConvertToType( value, property.PropertyType );
                property.SetValue( obj, convertedValue );
            }
    
            /// 
            /// Gets a  for the passed .
            /// 
            /// The property to examine. May not be null.
            /// A  with properties set to be used directly as is, never null.
            protected JsonPropertyAttribute GetJsonPropertyAttribute( PropertyInfo property )
            {
                if ( property == null )
                    throw new ArgumentNullException( "property" );
    
                object[] attributes = property.GetCustomAttributes( true );
    
                JsonPropertyAttribute jsonPropertyAttribute = null;
                bool ignore = false;
    
                foreach ( object attribute in attributes )
                {
                    if ( attribute is ScriptIgnoreAttribute )
                        ignore = true;
    
                    if ( attribute is JsonPropertyAttribute )
                        jsonPropertyAttribute = (JsonPropertyAttribute)attribute;
                }
    
                JsonPropertyAttribute result = jsonPropertyAttribute ?? new JsonPropertyAttribute();
                result.Ignored |= ignore;
    
                if ( string.IsNullOrWhiteSpace( result.PropertyName ) )
                    result.PropertyName = property.Name;
    
                if ( !this.ClassAttribute.DoNotLowerCaseFirstLetter && ( jsonPropertyAttribute == null || string.IsNullOrWhiteSpace( jsonPropertyAttribute.PropertyName ) ) )
                {
                    string name = result.PropertyName.Substring( 0, 1 ).ToLowerInvariant();
                    if ( result.PropertyName.Length > 1 )
                        name += result.PropertyName.Substring( 1 );
                    result.PropertyName = name;
                }
    
                return result;
            }
    
            #endregion
        }
    
        #endregion
    
        #region Class JsonClassAttribute
    
        /// 
        /// Attribute to be used in conjunction with .
        /// 
        /// 
        /// Decorate your class derived from  with this attribute to 
        /// manipulate how JSON serialization / deserialization is done for all properties of your derived class.
        /// 
        [AttributeUsage( AttributeTargets.Class )]
        public class JsonClassAttribute : Attribute
        {
            #region Properties
    
            /// 
            /// By default, all property names are automatically converted to have their first letter lower case (as it is convention in JavaScript). Set this to true to avoid that behavior.
            /// 
            public bool DoNotLowerCaseFirstLetter
            {
                get;
                set;
            }
    
            /// 
            /// By default, properties with value null are not serialized. Set this to true to avoid that behavior.
            /// 
            public bool SerializeNullValues
            {
                get;
                set;
            }
    
            #endregion
        }
    
        #endregion
    
        #region Class JsonPropertyAttribute
    
        /// 
        /// Attribute to be used in conjunction with .
        /// 
        /// 
        /// Among others, used to define a property's name when being serialized to JSON. 
        /// Implements some functionality found in Newtonsoft's JavaScript serializer, 
        /// see https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonPropertyAttribute.htm
        /// 
        [AttributeUsage( AttributeTargets.Property )]
        public class JsonPropertyAttribute : Attribute
        {
            #region Properties
    
            /// 
            /// True to ignore this property.
            /// 
            public bool Ignored
            {
                get;
                set;
            }
    
            /// 
            /// Gets or sets the name of the property. 
            /// 
            public string PropertyName
            {
                get;
                set;
            }
    
            /// 
            /// When true, the value of this property is serialized using value.ToString().
            /// 
            /// 
            /// Can be handy when serializing e.g. enums or types.
            /// Do not set this to true when deserialization is needed, since there is no general inverse method to ToString().
            /// When this is true, the property is just ignored when deserializing.
            /// 
            public bool UseToString
            {
                get;
                set;
            }
    
            #endregion
    
            #region Constructors
    
            /// 
            /// Default constructor
            /// 
            public JsonPropertyAttribute()
            {
            }
    
            /// 
            /// Initializes a new instance of the   class with the specified name. 
            /// 
            /// Name of the property
            public JsonPropertyAttribute( string propertyName )
            {
                this.PropertyName = propertyName;
            }
    
            #endregion
        }
    
        #endregion
    }
    

    And a unit test:

    #region CustomJavaScriptSerializer
    
    /// Tests behavior of CustomJavaScriptSerializer.
    [TestMethod]
    public void TestCustomJavaScriptSerializer()
    {
        // 11
        var dataItem11 = new JsonSerializeTest1();
        dataItem11.Label = "LabelName";
        dataItem11.Value = 5;
        dataItem11.Test = TestEnum.B;
        dataItem11.Tooltip = "TooltipName";
    
        string json11 = dataItem11.ToJson();
        Assert.IsTrue( json11 == "{\"label\":\"LabelName\",\"value\":5,\"test\":2,\"tooltext\":\"TooltipName\"}" );
    
        JsonSerializeTest1 deserialized11 = CustomJavaScriptSerializer.JsonDeserialize( json11 );
        Assert.IsNotNull( deserialized11 );
        Assert.IsTrue( deserialized11.Equals( dataItem11 ) );
    
        // 12
        var dataItem12 = new JsonSerializeTest1();
        dataItem12.Value = 5;
        dataItem12.Test = TestEnum.A;
        dataItem12.Tooltip = "TooltipName";
    
        string json12 = dataItem12.ToJson();
        Assert.IsTrue( json12 == "{\"value\":5,\"test\":1,\"tooltext\":\"TooltipName\"}" );
    
        JsonSerializeTest1 deserialized12 = CustomJavaScriptSerializer.JsonDeserialize( json12 );
        Assert.IsNotNull( deserialized12 );
        Assert.IsTrue( deserialized12.Equals( dataItem12 ) );
    
        // 21
        var dataItem21 = new JsonSerializeTest2();
        dataItem21.Label = "LabelName";
        dataItem21.Value = 5;
        dataItem21.Test = TestEnum.B;
        dataItem21.Tooltip = "TooltipName";
    
        string json21 = dataItem21.ToJson();
        Assert.IsTrue( json21 == "{\"Test\":\"B\",\"Label\":\"LabelName\",\"Value\":5}" );
    
        JsonSerializeTest2 deserialized21 = CustomJavaScriptSerializer.JsonDeserialize( json21 );
        Assert.IsNotNull( deserialized21 );
        Assert.IsTrue( deserialized21.Label == "LabelName" );
        Assert.IsTrue( deserialized21.Value == 5 );
        // No mistake! UseToString = true here. See JsonPropertyAttribute.UseToString. 
        Assert.IsTrue( deserialized21.Test == 0 );
        Assert.IsTrue( deserialized21.Tooltip == null );
    
        // 22
        var dataItem22 = new JsonSerializeTest2();
        dataItem22.Tooltip = "TooltipName";
    
        string json22 = dataItem22.ToJson();
        Assert.IsTrue( json22 == "{\"Test\":\"0\",\"Label\":null,\"Value\":null}" );
    
        JsonSerializeTest2 deserialized22 = CustomJavaScriptSerializer.JsonDeserialize( json22 );
        Assert.IsNotNull( deserialized22 );
        Assert.IsTrue( deserialized22.Label == null );
        Assert.IsTrue( deserialized22.Value == null );
        Assert.IsTrue( deserialized22.Test == 0 );
        Assert.IsTrue( deserialized22.Tooltip == null );
    
        var list = new List() { dataItem11, dataItem12 };
        var json = CustomJavaScriptSerializer.JsonSerialize( list );
        List deserialized = CustomJavaScriptSerializer.JsonDeserialize>( json );
        Assert.IsNotNull( deserialized );
        Assert.IsTrue( deserialized.Count == 2 );
        Assert.IsTrue( deserialized[ 0 ].Equals( deserialized11 ) );
        Assert.IsTrue( deserialized[ 1 ].Equals( deserialized12 ) );
    }
    
    
    [JsonClass( DoNotLowerCaseFirstLetter = true, SerializeNullValues = true )]
    public class JsonSerializeTest2 : JsonSerializeTest1
    {
        /// 
        /// A tooltip
        /// 
        [JsonProperty( Ignored = true )]
        public override string Tooltip
        {
            get;
            set;
        }
    
        /// 
        /// An enum
        /// 
        [JsonProperty( UseToString = true )]
        public override TestEnum Test
        {
            get;
            set;
        }
    }
    
    public class JsonSerializeTest1 : CustomJavaScriptSerializer
    {
        /// 
        /// A label
        /// 
        public virtual string Label
        {
            get;
            set;
        }
    
        /// 
        /// A Value
        /// 
        public virtual decimal? Value
        {
            get;
            set;
        }
    
        /// 
        /// An enum
        /// 
        public virtual TestEnum Test
        {
            get;
            set;
        }
    
        /// 
        /// A tooltip
        /// 
        [JsonProperty( "tooltext" )]
        public virtual string Tooltip
        {
            get;
            set;
        }
    
        /// 
        /// Whether this object is the same as .
        /// 
        /// True =  is the same as this object, false otherwise.
        public override bool Equals( object obj )
        {
            var other = obj as JsonSerializeTest1;
    
            // Cast to object to avoid that it calls overridden == operator here.
            if ( (object)other == null )
                return false;
    
            return this.Label == other.Label && this.Value == other.Value && this.Test == other.Test && this.Tooltip == other.Tooltip;
        }
    
        /// 
        /// Get hash code for comparison
        /// 
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
    
    public enum TestEnum
    {
        A = 1,
        B = 2
    }
    
    #endregion
    

    Enjoy!

提交回复
热议问题