Elasticsearch-net: How to use TypeNameHandling to TypeNameHandling.Auto for deserialization only

Created on 16 Dec 2014  路  13Comments  路  Source: elastic/elasticsearch-net

I need to set the TypeNameHandling to TypeNameHandling.Auto to deserialize my objects returned by elasticsearch. I set the settings

new ConnectionSettings(uri).SetJsonSerializerSettingsModifier(s => s.TypeNameHandling = TypeNameHandling.Auto)

But this also change the type name handling of when sending the query. So Nest sends the query with multiple $type in it which doesn't work :

{
  "_source": {
    "$type": "Nest.SearchSourceDescriptor`1[[Pelican.Web.Models.Search.EntityContainer`1[[Pelican.Models.FormModel, Pelican.Models]], Pelican.Web]], Nest",
    "include": [
      "Name",
      "Date",
      "Data"
    ]
  },
  "query": {
    "filtered": {
      "$type": "Nest.FilteredQueryDescriptor`1[[Pelican.Web.Models.Search.EntityContainer`1[[Pelican.Models.FormModel, Pelican.Models]], Pelican.Web]], Nest",
      "filter": {
        "terms": {
          "Name": [
            "Document name"
          ],
          "execution": "or"
        }
      }
    }
  }
}

So, how can I set the deserialization settings of Newtownsoft without touching the serialization?

Most helpful comment

Instead of globally setting the the type name handling you can also mark properties with:

[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]

There is also a ItemTypeNameHandling option if you are working with collections.

This is the safer way adding support for this only local to your objects.

Hope this solves your issue!

All 13 comments

Hey @alexandrepepin out of curiosity, why are you trying to do this? NEST will automatically deserialize to whatever type you specified when building the request- you shouldn't need to mess with the Json.Net serialization settings to accomplish this. Is there some special case you are trying to handle?

I need to set this setting because my object contains multiple interfaces. Newtonsoft needs to know the right implementation in order to deserialize. To do that, when the type isn't the same as the property and the setting TypeNameHandling is set to Auto, Newtonsoft writes the real type in a special $type in the object

For example, my object has a structure similar to this :

public class MyObject
{
    public IMyInterface MyInterface;
}

public interface IMyInterface
{
    string SomeProperty { get; set; }
}

public class MyInterfaceImplementation : IMyInterface
{
    public string SomeProperty { get; set; }
}

Let say I want to serialize and deserialize MyObject

MyObject myObject = new MyObject() { MyInterface = new MyInterfaceImplementation() { SomeProperty = "property " } };
string serialized = JsonConvert.SerializeObject(myObject);
MyObject deserialized = JsonConvert.DeserializeObject<MyObject>(serialized);

This will throw Newtonsoft.Json.JsonSerializationException with the message Could not create an instance of type CSharpTest.Program+IMyInterface. Type is an interface or abstract class and cannot be instantiated. Path 'MyInterface.SomeProperty'

So in order to make the serialization and deserialization works, Newtonsoft needs to know which class was MyInterface. We do that by setting TypeNameHandling = TypeNameHandling.Auto

If I retry my example with the setting, it can serialize and deserialize without problem

MyObject myObject = new MyObject() { MyInterface = new MyInterfaceImplementation() { SomeProperty = "property " } };
JsonSerializerSettings settings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto };
string serialized = JsonConvert.SerializeObject(myObject, settings);
MyObject deserialized = JsonConvert.DeserializeObject<MyObject>(serialized, settings);

The down side of this is that Newtonsoft writes the class type in the property $type in the Json in order to know which class to instantiate

{
  "MyInterface": {
    "$type": "CSharpTest.Program+MyInterfaceImplementation, CSharpTest",
    "SomeProperty": "property "
  }
}

This is why I need to set that setting when NEST deserialize my object.

My current workaround is to tell NEST to deserialize in JObject in the search and after I deserialize again with Newtonsoft to my object with the good settings. But this slows dramatically the total search time because I need to deserialize two times

Instead of globally setting the the type name handling you can also mark properties with:

[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]

There is also a ItemTypeNameHandling option if you are working with collections.

This is the safer way adding support for this only local to your objects.

Hope this solves your issue!

Thx for the info, I didn't know we could do that.

Unfortunately, it doesn't fix my issue because of a bug of Newtownsoft. I reported it here : https://github.com/JamesNK/Newtonsoft.Json/issues/450

Do you have any other solution?

I think we should be able to set the settings for the deserialization without affecting the internal serialization settings of Nest

@alexandrepepin the only way that would be possible if all our classes have dedicated jsonconverters for reading and writing for them to be completely isolated. Which would introduce a major strain on development if we have to maintain those manually.

Since this is now mainly a JSON.NET bug i'm closing this.

No, it is not a bug of Newtonsoft. He closed my issue also. The attribute applies to the contents of the dictionary (i.e. List<>), not the contents of the list. I can't use the attribute in my case. I really need to be able to change the deserialization settings

Can you use:

[JsonObject(TypeNameHandling)]

On IMyInterfaceType itself? Would that solve it?

No, it does not work either. Here is a more complete example showing my use case

static void Main(string[] args)
{
    DataType data = new DataType();
    List<IMyInterfaceType> myInterfaceType = new List<IMyInterfaceType>();
    myInterfaceType.Add(new MyInterfaceImplementationType() { SomeProperty = "property" });
    myInterfaceType.Add(new MySecondInterfaceImplementationType() { SomeProperty = "property2" });
    data.Rows.Add("key", myInterfaceType);

    string serialized = JsonConvert.SerializeObject(data, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });

    DataType deserialized = JsonConvert.DeserializeObject<DataType>(serialized);
    // This works : 
    // DataType deserialized = JsonConvert.DeserializeObject<DataType>(serialized, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });
}

public class DataType
{
    public DataType()
    {
        Rows = new Dictionary<string, IEnumerable<IMyInterfaceType>>();
    }

    public Dictionary<string, IEnumerable<IMyInterfaceType>> Rows { get; private set; }
}

[JsonObject(ItemTypeNameHandling = TypeNameHandling.Auto)]
public interface IMyInterfaceType
{
    string SomeProperty { get; set; }
}

public class MyInterfaceImplementationType : IMyInterfaceType
{
    public string SomeProperty { get; set; }
}

public class MySecondInterfaceImplementationType : IMyInterfaceType
{
    public string SomeProperty { get; set; }
}

I can't get the deserialization working without setting the JsonSerializerSettings to the same I used to serialize.

Ok then while not a bug it should be a feature request on JSON.NET to be able to specify it at the interface level.

I understand your frustration but we are all users of json.net here and your needs should be able to be specified locally on your scope only. By saying to us you expect to to alter a pretty intrusive setting globally without altering how _our_ objects are serialized you are looking at it the wrong way IMO.

You can write a custom converter for now and apply it to your IMyInterfaceType for the time being. With NEST 2.0 we are exploring options at generating the serializing/deserialization of our objects ahead of time (no longer relying on JSON.NET defaults (perhaps not using JSON.NET at all)) but this will take a while to implement.

Ok I understand. Thanks for your help :)

@alexandrepepin this may be a slightly old answer but I got here and there via google, so thought I'd share just in case it helped -- http://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net-to-deserialize-a-list-of-base

One of the answers looks like you could look for a specific property in the object to determine what to deserialize to. Another question suggested a try/parse model internally.

Instead of globally setting the the type name handling you can also mark properties with:

[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]

There is also a ItemTypeNameHandling option if you are working with collections.

This is the safer way adding support for this only local to your objects.

Hope this solves your issue!

Thx for the info

Was this page helpful?
0 / 5 - 0 ratings