Allow list of JSON serializers to be customised

  1. #1

    Allow list of JSON serializers to be customised

    I have a class with a property of type MongoDB.Bson.ObjectId. When this is serialized using the default set of serializers in Ext.NET, it comes back as an empty string. I would like to be able to add my own JsonSerializer to the list in the Ext.Net.JSON class, but this isn't possible, because the serializers are returned from a public static property getter and are cached in a private static variable.

    I've got a current nasty hack to use reflection to get the private static field, cast it as a List<JsonConverter>, and add my converter to that, but I'd really rather not :)
  2. #2
    Can you elaborate on this a bit more.

    Do you need to pass your own list of Converters to the JSON.Serialize Method?

    Do you want the ability to add/customize/override the List of JsonConverters returned from the .Converters property?

    public static List<JsonConverter> Converters
    {
        get
        {
            List<JsonConverter> converters = new List<JsonConverter>();
            converters.Add(new JSONDateTimeJsonConverter());
            converters.Add(new EnumJsonConverter());
            converters.Add(new GuidJsonConverter());
            converters.Add(new JFunctionJsonConverter());
            converters.Add(new JRawValueJsonConverter());
    
    
            return converters;
        }
    }
    Any chance you could post a really simple sample demonstrating the existing scenario? Not the Reflection stuff you're doing, just a simple mock of the Class and how (or what) is calling JSON.Serialize. To demonstrate empty return values.
    Geoffrey McGill
    Founder
  3. #3
    Thanks for the quick reply. Here is a cut-down version of the class I'm using:

    public class ContentItem
    {
        public MongoDB.Bson.ObjectId ID { get; set; }
        public string Name { get; set; }
        // etc.
    }
    I have a page with a GridPanel, that loads a collection of ContentItems through a Store / ArrayReader:

    <ext:Store runat="server" ID="exsDataStore" OnRefreshData="exsDataStore_RefreshData">
    	<Reader>
    		<ext:ArrayReader IDProperty="ID">
    			<Fields>
    				<ext:RecordField Name="ID" Mapping="ID" Type="Auto" />
    				<ext:RecordField Name="Name" Mapping="Name" />
    			</Fields>
    		</ext:ArrayReader>
    	</Reader>
    </ext:Store>
    
    <ext:GridPanel runat="server" ID="gpaChildren" StoreID="exsDataStore" StripeRows="true" Border="false">
    // Not important
    </ext:GridPanel>
    
    protected void exsDataStore_RefreshData(object sender, StoreRefreshDataEventArgs e)
    {
    	// Actually the data comes from elsewhere, I don't really create a new array every time
    	exsDataStore.DataSource = new[] { new ContentItem { ID = ObjectId.GenerateNewId(), Name = "Test" } };
    	exsDataStore.DataBind();
    }
    I have looked at the javascript that gets rendered from the Store. It creates a pagingMemoryProxy, which includes an array of serialised ContentItems that have the Name property serialised correctly. But the ID property is missing. (This whole thing worked fine back when ID was an integer, but since I changed it to MongoDB.Bson.ObjectID, it has stopped working.)

    As you can see, I'm not directly calling JSON.Serialize (if I was, I could use the overload that accepts a list of JsonConverter objects). I believe that in my case, JSON.Serialize is called from StoreDataBound.BindRecord. So I need to be able to add to the global list of JsonConverter's used by Ext.NET, so that wherever JSON.Serialize is called internally, it will pick up my custom ObjectIdJsonSerializer.
    Last edited by timjones; Feb 25, 2012 at 3:59 AM.
  4. #4
    Thanks for the update.

    What is rendered to the page if you run the following test. You'll have to add whatever references are required to enable the ObjectId.GenerateNewId call.

    Example

    <%@ Page Language="C#" %>
    
    
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
    
    
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            this.Response.Write(JSON.Serialize(ObjectId.GenerateNewId()));
        }
    </script>
    <!DOCTYPE html>
    <html>
    <head runat="server">
        <title>Ext.NET Example</title>
    </head>
    <body>
    </body>
    </html>
    I think the Serializer is just going to call .ToString() on the object to get it's serialized form. What's the value returned by calling ObjectId.GenerateNewId().ToString()?
    Geoffrey McGill
    Founder
  5. #5
    Earlier today I posted a sample on StackOverflow that might help with your scenario, see

    http://stackoverflow.com/questions/9...436791#9436791

    Specifically, see the strProjects configuration and the use of .ServerMapping property.

    What happens if you change your "ID" RecordField config to the following:

    Example

    <ext:RecordField Name="ID" ServerMapping="ID.Pid" />
    I found that .Pid might be the property needed from the following location, see

    http://api.mongodb.org/csharp/1.0/ht...9972ec3e65.htm

    You can replace .Pid with any other ObjectId property name.

    Hope this helps.
    Geoffrey McGill
    Founder
  6. #6
    A third option that "should" work is creating your own JsonConverter class, then applying as an Attribute on the ContentItem.ID property.

    Example

    public class ContentItem
    {
        [JsonConverter(typeof(MongoDBObjectIdJsonConverter))]
        public MongoDB.Bson.ObjectId ID { get; set; }
    
    
        public string Name { get; set; }
        // etc.
    }
    Here's a copy of a custom JsonConverter class we use within Ext.NET that you should be able to use as starting point. The class would have to reside in your project. When the ContentItem is run through JSON.Serialize, the JsonConverterAttribute should be read the Serializer will run the object through your custom JsonConverter class.

    Example

    /********
     * @version   : 1.3.0 - Ext.NET Pro License
     * @author    : Ext.NET, Inc. http://www.ext.net/
     * @date      : 2012-02-21
     * @copyright : Copyright (c) 2007-2012, Ext.NET, Inc. (http://www.ext.net/). All rights reserved.
     * @license   : See license.txt and http://www.ext.net/license/. 
     ********/
    
    
    using System;
    using System.ComponentModel;
    
    
    using Newtonsoft.Json;
    
    
    namespace Ext.Net
    {
    	/// <summary>
    	/// 
    	/// </summary>
    	[Description("")]
        public partial class CtorDateTimeJsonConverter : ExtJsonConverter
        {
    		/// <summary>
    		/// 
    		/// </summary>
    		[Description("")]
            public override bool CanConvert(Type valueType)
            {
                return typeof(DateTime).IsAssignableFrom(valueType);
            }
    
    
    		/// <summary>
    		/// 
    		/// </summary>
    		[Description("")]
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                if (value is DateTime)
                {
                    DateTime date = (DateTime)value;
    
    
                    if (date.Equals(DateTime.MinValue))
                    {
                        writer.WriteRawValue("null");
                    }
                    else
                    {
                        string template = (date.TimeOfDay == new TimeSpan(0, 0, 0)) ? "{0},{1},{2}" : "{0},{1},{2},{3},{4},{5}";
    
    
                        writer.WriteStartConstructor("Date");
                        writer.WriteRawValue(
                            string.Format(template, date.Year, date.Month - 1, date.Day,
                                          date.Hour, date.Minute, date.Second));
                        writer.WriteEndConstructor();
                    }
                }
            }
    
    
    		/// <summary>
    		/// 
    		/// </summary>
    		[Description("")]
            public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                throw new NotImplementedException();
            }
        }
    }
    Hope this helps.
    Geoffrey McGill
    Founder
  7. #7
    Okay, I have a fourth option. Add a "Proxy" property to your class and return a native object.

    The following sample uses the [JsonIgnore] Attribute to prevent the .ID property from being Serialized. The [JsonProperty] attribute is then used to Serialize a "Proxy" .ID value.

    Example

    [JsonIgnore]
    public MongoDB.Bson.ObjectId ID { get; set; }
                
    [JsonProperty("ID")]
    public short IDProxy { 
        get
        {
            if (this.ID == null)
            {
                this.ID = ObjectId.GenerateNewId();
            }
                    
            return this.ID.Pid;
        }
    }
    Hope this helps.
    Geoffrey McGill
    Founder
  8. #8
    Hopefully one of these should help work around the issue. Keep us updated.
    Geoffrey McGill
    Founder
  9. #9
    Thanks for all the suggestions!

    1) I tried your test page, and here's what it produces:
    {"Timestamp":1330149592,"Machine":3974333,"Pid":70 16,"Increment":9127447,"CreationTime":"2012-02-25T05:59:52"}

    However, calling .ToString() directly on an ObjectId produces a simple string, i.e.:
    4f487ec8c9c1811b687567f35

    If JSON.Serialize did indeed result in a call to .ToString(), that would be great, since that's the output I want, but it seems to be treating it as a complex object.

    2) Unfortunately I need all the ObjectId properties in order to recreate it on the server-side, so I can't use a ServerMapping to get at a specific property.

    3) Decorating the ContentItem.ID property with a serializer would work, but I'm not able to make that kind of change to ContentItem.

    4) Good idea, I'm sure that will work. It's not that elegant, though, so I still think registering a custom "global" serializer with Ext.NET would be better. Especially since there might be other ObjectId properties, I'd need to provide a proxy property for each one.

    By the way, I found that if I create a derived Store control, and override IsBindableType like this, I can get part of the way there:

    public override bool IsBindableType(System.Type type)
    {
    	if (type == typeof(ObjectId))
    		return true;
    	return base.IsBindableType(type);
    }
    This means ID does get included in the rendered data, but it's still serializing it as a complex object.

    So I'll go with (4) for now, but I'd still like the ability to register custom global JsonSerializer's, if it's at all possible. I understand that it's an edge case though.
  10. #10
    Quote Originally Posted by timjones View Post
    2) Unfortunately I need all the ObjectId properties in order to recreate it on the server-side, so I can't use a ServerMapping to get at a specific property.
    Here's a fifth option of setting .IsComplex="true" on the RecordField. The trick with this one is you need a Renderer on the Column to pull the object property out (if you need to render it). If you don't need to render that RecordField, then the object is available from the Store Record as a serialized object literal.

    Example

    <%@ Page Language="C#" %>
    
    <%@ Import Namespace="System.Collections.Generic" %>
    
    
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
    
    
    <script runat="server">
       protected void Page_Load(object sender, EventArgs e)
       {
           // Owners
           
           var owners = new List<Person>();
    
    
           var billy = new Person
           {
               ID = 1,
               Name = "Billy"
           };
           
           var frank = new Person 
           {
                   ID = 2,
                   Name = "Frank"
           };
    
    
           var jane = new Person
           {
               ID = 3,
               Name = "Jane"
           };
           
           owners.AddRange(new Person[] { billy, frank, jane });
    
    
           this.strOwners.DataSource = owners;
           this.strOwners.DataBind();
    
    
           
           // Projects
    
    
           var projects = new List<Project>();
    
    
           projects.AddRange(new Project[] { 
               new Project { 
                   ID = 1,
                   Name = "Project A",
                   Owner = billy
               },
               new Project {
                   ID = 2,
                   Name = "Project B",
                   Owner = frank
               },
               new Project {
                   ID = 3,
                   Name = "Project C",
                   Owner = jane
               }
           });
    
    
           this.strProjects.DataSource = projects;
           this.strProjects.DataBind();
       }
    
    
       public class Project
       {
           public int ID { get; set; }
           public string Name { get; set; }
           public Person Owner { get; set; }
       }
    
    
       public class Person
       {
           public int ID { get; set; }
           public string Name { get; set; }
       }
    </script>
    <!DOCTYPE html>
    <html>
    <head runat="server">
       <title>Ext.NET Example</title>
    </head>
    <body>
    <form runat="server">
       <ext:ResourceManager runat="server" />
       
       <ext:GridPanel 
           runat="server" 
           Title="Example" 
           Height="350" 
           Width="500"
           AutoExpandColumn="Owner">
           <Bin>
               <ext:Store ID="strOwners" runat="server">
                   <Reader>
                       <ext:JsonReader>
                           <Fields>
                               <ext:RecordField Name="ID" Type="Int" />
                               <ext:RecordField Name="Name" />
                           </Fields>
                       </ext:JsonReader>
                   </Reader>
               </ext:Store>
           </Bin>
           <Store>
               <ext:Store ID="strProjects" runat="server">
                   <Reader>
                       <ext:JsonReader>
                           <Fields>
                               <ext:RecordField Name="ID" Type="Int" />
                               <ext:RecordField Name="Name" />
                               <ext:RecordField Name="Owner" IsComplex="true" />
                           </Fields>
                       </ext:JsonReader>
                   </Reader>
               </ext:Store>
           </Store>
           <ColumnModel runat="server">
               <Columns>
                   <ext:Column Header="ID" DataIndex="ID" />
                   <ext:Column Header="Name" DataIndex="Name" />
                   <ext:Column ColumnID="Owner" Header="Owner" DataIndex="Owner">
                       <Renderer Handler="return value.Name;" />
                   </ext:Column>
               </Columns>
           </ColumnModel>
       </ext:GridPanel>
    </form>
    </body>
    </html>
    3) Decorating the ContentItem.ID property with a serializer would work, but I'm not able to make that kind of change to ContentItem.
    Both #3 and #4 would require adding a property Attribute. If one is possible, than the other should be possible too.

    As well, out of interests sake... why is #2 (ServerMapping) not possible, but #4 (proxy property) possible? Both would only render the "Pid" to the Store.

    Hope this helps.
    Geoffrey McGill
    Founder

Similar Threads

  1. List of Icons?
    By Tbaseflug in forum 1.x Help
    Replies: 1
    Last Post: Jan 05, 2011, 4:31 PM
  2. [CLOSED] Problems with Customised Grid Panel (0.8.3) Headers
    By vedagopal2004 in forum 1.x Legacy Premium Help
    Replies: 9
    Last Post: Aug 30, 2010, 1:06 PM
  3. Replies: 12
    Last Post: Feb 26, 2010, 4:51 AM
  4. [1.0] Wish List
    By r_honey in forum Open Discussions
    Replies: 2
    Last Post: Jan 14, 2010, 7:45 AM
  5. Replies: 5
    Last Post: Dec 18, 2009, 9:50 AM

Posting Permissions