Custom create object, but keep property mapping and value assignment?

May 6, 2009 at 7:20 AM

I need to create an object with a custom method/factory, but keep the property mapping to the object.
So I create a custom object, but let Json.NET assign the Json property values to the custom object properties.

Something like this...

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }    
}

public class MyJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value)
    {
        //omitted
    }

    public override object ReadJson(JsonReader reader, Type objectType)
    {
        JObject o = JObject.Load(reader);

        Customer customer = //SomeFactoryMethod;

        //TODO:
        //Here I would like to assign all properties from the Json obejct to the customer
    

        return customer;
    }

    public override bool CanConvert(Type objectType)
    {
        //omitted
    }
}

I need this to work on many different classes, so the cant use the Customer properties directly.

Coordinator
May 6, 2009 at 9:53 AM
Right now there is no way to do that.

Having a way to customize how an object is created is something I have wanted to add for a while, but no way to do it has jumped out at me as incredibly clean or elegant yet.
May 6, 2009 at 10:19 AM
My best bet would be to be able to register custom factories for types, just like the custom converters.

In what file do you do an Activator.CreateInstance (or similar)??

Perhaps I can look at it, and come up with a posible solution?

/Morten Lyhr
May 7, 2009 at 9:26 PM
I need the same functionality for being able to (de)serialize an (potentialy cyclic) object graph (and not an object tree). A graph means that references to objects needs to be serialized also, and that a reference can occur before or after the contents of the object is defined. Therefore I want to create an object when the first reference is seen, and then fill the already created object when the content is seen.

Digging through the code revealed that I could probably write a custom converter and use JsonSerializer.CreateObject() with a non-null third argument 'existingValue', if it were only public instead of private.

Would that be possible?
Coordinator
May 8, 2009 at 8:47 AM

eludias supporting cyclic references is something I have wanted to add for a while. The work I did on JSON schema already has it and at some point I'm going to investigate adding it to the serializer.

Coordinator
May 8, 2009 at 8:47 AM

I am thinking of changing the JsonConverter signature so that it passes in the JsonSerializer instance to ReadJson and WriteJson. Then on JsonSerializer I would add a Deserialize overload that you could pass an object instance to and would have the values from JSON populated on it.

It would end up looking something like this:

public class MyJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //omitted
    }

    public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
    {
        JObject o = JObject.Load(reader);

        Customer customer = //SomeFactoryMethod;

        serializer.Deserialize(o.CreateReader(), customer);    

        return customer;
    }

    public override bool CanConvert(Type objectType)
    {
        //omitted
    }
}

The only thing I don't like is reusing the name Deserialize. Maybe 'Populate' instead?

Thoughts?

May 8, 2009 at 7:49 PM

I do agree that a JsonSerializer.Deserialize() which takes not a type but an (instantiated) object makes sense. Even the name makes sense, although at first reading the name did not make sense. Other names crossing my mind were Merge, DeserializeInPlace, DeresializeAndMerge, but Populate could also be chosen. However, because in essence it is about deserialization and were it goes is a secondary concern, Deserialize sounds most logical.

Having the JsonSerializer as an argument does not make that much sense to me since:

  - it can currently be done already without changing the interface of the library

  - you probably don't want to do that anyway

I'll explain it a little: My current solution in the ADO.NET Entitity serializer is to have an instance variable 'JsonSerializer' which works OK:

        /// <summary>Created on-demand</summary>
        private JsonSerializer serializer;

        /// <summary>Constructor</summary>
        public MyConverter(string[] namespacesToRemove) {
            serializer = JsonSerializer.Create(settings);
        }

        /// <summary>
        /// Returns the serializer with settings used for (de)serializing the value of the object
        /// (the second element of the array).
        ///
        /// The on-demand thing is needed to be able to create recursive settings:
        /// settings which have a converter which has as settings these settings again.
        /// Since JsonSerializer.Create() copies the current settings instead of
        /// keeping the reference, we have to do it like this.
        /// </summary>
        /// <returns></returns>
        private JsonSerializer Serializer() {
            if (serializer == null) {
                serializer = JsonSerializer.Create(settings);
            }
            return serializer;
        }

        public override object ReadJson(JsonReader reader, Type objectType) {
            var j = JObject.Load(reader);
            var valueTokenReader = new JsonTokenReader(j);
            object value = Serializer().Deserialize(valueTokenReader, MyClass);
            return value;
        }

 

The problem with re-using the same serializer within a converter is that you get easily into an endless loop: ReadJson will call serializer.Deserialize(), which will call the same ReadJson again if CanConvert() will return true for both cases. This was the case for a wrapping-converter I wrote. Basically, it is a converter which adds one property to all objects serialized: the .NET type, so one is able to deserialize runtime variable types. Excerpts from the converter:

    /// <summary>
    /// A converter which serialises not only the properties, but also the class
    /// (type) of the object as an extra JSON object propery.
    ///
    /// One would use a converter like this whien the type is not exactly known
    /// or varies. For example, when a member might contain a subclass of the type
    /// specified, or when the member is an interface.
    /// </summary>
    public class TypedObjectToJsonObjectConverter : JsonConverter {
        private JsonSerializer Serializer() {
            if (serializer == null) {
                serializer = JsonSerializer.Create(settings);
            }
            return serializer;
        }

        public override void WriteJson(JsonWriter writer, object value) {
            JToken j;
            using (var jsonWriter = new JsonTokenWriter()) {
                // Disable this serializer during the next Serialize() call:
                cannotConvertOnce = true;
                Serializer().Serialize(jsonWriter, value);
                // Reenable this serializer:
                cannotConvertOnce = false;
                j = jsonWriter.Token;
            }
            string typeAsString = value.GetType().ToString();
            (j as JObject).AddFirst(new JProperty("CLRType", typeAsString));
            Serializer().Serialize(writer, j);
        }

        public override bool CanConvert(Type objectType) {
            if (cannotConvertOnce) {
                cannotConvertOnce = false;
                return false;
            }
            return true;
        }

        /// <summary>Constructor</summary>
        public TypedObjectToJsonObjectConverter(JsonSerializerSettings settings) {
            this.settings = settings;
        }
    }

The big, big hack is here the 'cannotConvertOnce': I want to use the same serializer settings for serializing the objects underneath the current object, but I also have to change the settings a little otherwise I will get into an endless loop.

 

[In this case, I do not need a list of converts of which one shall be called, but I need a pipeline of converters where each converter can modify the result of the result of the pipeline till now].

 

I will post the complete code of the converters shortly, if time permits.

 

Coordinator
May 8, 2009 at 11:45 PM
Edited May 8, 2009 at 11:47 PM

I prefer Populate over deserialize because the method isn't creating the object, you are passing it in. Also current overloads of Deserialize return an object which wouldn't make sense in this case. Having some overloads of Deserialize returning an object and some returning void strikes me as wrong.

Populate wouldn't look for JsonConverters. It would only loop through the current JSON structure and copy the values onto the object you pass in (another reason why I would prefer a different name from Deserailize). That would get around the loop problem.

Finally there isn't any state stored in the JsonSerializer class which would prevent it from being called recursively (I think, need to test to make sure). Having an instance of JsonSerializer with the correct settings inside ReadJson and WriteJson I can imagine as being quite useful in some cases. It looks like you have worked around that problem but I would like it to be simple for other users.

May 10, 2009 at 7:31 PM

Ah, ok, I did misunderstood. So there are big differences between the other Deserializes, which makes 'Populate' a good candidate for a name.

But if the configuration of the JsonSerializer is not used, then I don't see a reason for passing the JsonSerializer as an argument. What could the difference be between (in the example you showed):

        serializer.Populate(o.CreateReader(), customer);    

and

        (new JsonSerializer()).Populate(o.CreateReader(), customer);

(except for performance differences, maybe)? Since Populate does not use the configuration nor the state of the JsonSerializer, there's no information left in the JsonSerializer passed in, as far as I can see.

Coordinator
May 11, 2009 at 2:09 AM
Edited May 13, 2009 at 1:41 AM

Populate will use the configuration of the JsonSerializer. For example say the JSON contains dates which will be populated onto an object, the serializer will need to use whatever date JsonConverter is registered against it. Populate only won't use a JsonConverter for the initial value to be populated on.

Also another reason is I am going to be adding reference support to the JsonSerializer, i.e. for a collection of employees...

[
  {
    "$id": 1,
    "Name": "John",
    "Manager": null,
  },
  {
    "$id": 2,
    "Name": "Glenn",
    "Manager": {"$ref":1}
  }
]

I need to track state (a collection of objects with their ids) inside the JsonSerializer and therfore include it as an argument to the JsonConverter methods.

Coordinator
May 13, 2009 at 10:27 AM

Checked in. That was hard.