How does JSON.NET serialize objects when using EF table-per-hierarchy? - asp.net

I'm using Entity Framework Code First table-per-hierarchy in my ASP.NET Web API project. One of my models has a List that is of the type of the abstract base class of the hierarchy.
List<Dog> // (for example; with GoldenRetriever, BorderCollie, etc inheriting)
I'm trying to test POSTing some data to my API Controller using Fiddler. But I don't know how to represent the JSON when I do so. If I try something like:
"Dogs":
[
{"Name":"Bud", "Age":"3"}
]
I get the error:
"Could not create an instance of type Models.Dog. Type is an interface
or abstract class and cannot be instantiated."
Specifying the Discriminator in the JSON doesn't help me either. Anyone have any ideas? Thanks!
Edit: Solution
The trick is to use the $type property in the JSON string. For more information see this link suggested by m.t.bennett in the comments.
To enable using the $type property I needed to add the following to WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling
= TypeNameHandling.Auto;
Then when posting the JSON to Fiddler, I added the $type property with the full object path:
{"$type":"Example.Models.Dogs.GoldenRetriever, Example.Models",
"Name":"Bud","Age":3}
For me to figure out this formatting I used Snixtor's suggestion to serialize an object and output the JSON string. Brilliant!
I'm not sure if this is the most elegant solution since it's JSON.NET specific, but it works!

I used a custom JsonConverter to handle the base-type deserialization.
public override bool CanConvert(Type objectType) {
return typeof(Mybase).Equals(objectType);
}
public override MyBase Deserialize(JsonReader reader, MyBase existingValue, JsonSerializer serializer) {
var ret = existingValue;
var jobj = JObject.ReadFrom(reader);
if (jobj["type"] == null || jobj["type"].Type != JTokenType.String)
throw new JsonSerializationException("Supplied JSON is missing the required 'type' member.");
var typeName = jobj["type"].Value<string>();
var t = this.GetType().Assembly.GetType("MyNamespace." + typeName);
if(t == null)
throw new JsonSerializationException(String.Format("Could not identify supplied type '{0}' as a known type.", typeName));
if (existingValue != null && !t.IsInstanceOfType(existingValue))
throw new JsonSerializationException(String.Format("Type Mismatch: existingValue {0} is not assignable from supplied Type {1}", existingValue.GetType().Name, t.Name));
ret = (ContactMethod)jobj.ToObject(t);
return ret;
}
And registered the JsonConverter durring application initialization:
JsonSerializerSettings settings;
settings.Converters.Add(new MyBaseConverter());

Related

JsonConverter resolve reference

I have a custom JsonConverter which handles the creation of derived types during deserialization, in most cases this works as expected.
The situation where I have an issue is, when there are referenced objects in the json structure.
Is it possible to rely on the default deserialization when we detect a reference? What should the ReadJson method return?
In the sample below we return null in case of a reference.
if (reader.TokenType == JsonToken.Null) return null;
var jObject = JObject.Load(reader);
JToken token;
if (jObject.TryGetValue("$ref", out token))
{
return null;
}
Or must we implement a custom ReferenceResolver as the default one can't be used in the converter (only internal use)?
Any suggestions are welcome.
After some extra testing, I found the solution myself. When I first was trying using the default ReferenceResolver I got an exception saying "The DefaultReferenceResolver can only be used internally.". This pointed my in the wrong direction, you can use the DefaultReferenceResolver in your converter but I was calling it the wrong way...
Solution:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var jObject = JObject.Load(reader);
string id = (string)jObject["$ref"];
if (id != null)
{
return serializer.ReferenceResolver.ResolveReference(serializer, id);
}
// Custom instance creation comes here
}

Unexpected error when deserializing unknown types with Json.Net

In the following code, I serialize an object using Json.Net. This Json has type names embedded. I then change one of the type names to induce an error (this is a test, I am dealing with a real issue in an existing project). When I deserialize the Json, I expect to get an object back that has a null value for the property with the fiddled type name. Instead the serializer craps out and returns null. Are my expectations correct? Can I change the settings somehow so that I will get a non-null object for my root object? Note that the second error that I get suggests that there is a bug in the serializer.
static public class JsonTest
{
static public void Test()
{
// Create test object
A a = new A
{
MyTest = new MyTest(),
};
// Serialize it.
string json = JsonConvert.SerializeObject(a, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
// Fiddle class name to induce error
json = json.Replace("+MyTest", "+MyTest2");
// Before: {"MyTest":{"$type":"<Namespace>.JsonTest+MyTest, <Assembly>"}}
// After: {"MyTest":{"$type":"<Namespace>.JsonTest+MyTest2, <Assembly>"}}
// Deserialize
A a2 = JsonConvert.DeserializeObject<A>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Error = (object sender, ErrorEventArgs e) =>
{
e.ErrorContext.Handled = true; // Should have only one error: the unrecognized Type
}
});
// A second error occurs: Error = {Newtonsoft.Json.JsonSerializationException: Additional text found in JSON string after finishing deserializing object....
// a2 is null
}
public class A
{
public ITest MyTest { get; set; }
}
public interface ITest { }
public class MyTest : ITest { }
}
Update
This issue has been fixed in Json.NET 10.0.2 in this submission.
Original Answer
This looks to be a bug in Json.NET. If I set JsonSerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead then the problem goes away:
// Deserialize
A a2 = JsonConvert.DeserializeObject<A>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead,
Error = (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs e) =>
{
Debug.WriteLine(e.ErrorContext.Path);
e.ErrorContext.Handled = true; // Should have only one error: the unrecognized Type
}
});
Debug.Assert(a2 != null); // No assert.
However, it should not be necessary to turn on this setting, which enables reading metadata properties including "$type" located anywhere in a JSON object, rather than just as the first property. Most likely it coincidentally fixes the bug since it requires pre-loading the entire JSON object before beginning deserialization.
You could report an issue if you want.
Debugging a bit, the problem seems to be that, because the inner MyTest object cannot be constructed, the exception is caught and handled by JsonSerializerInternalReader.PopulateObject() while populating the outer object A. Because of this, the JsonReader does not get advanced past the inner, nested object, leaving the reader and serializer in an inconsistent state. This accounts for the second exception and the eventual Additional text found in JSON string after finishing deserializing object. Path '' exception.

Error deserializing IEnumerable<byte> properties with json.net 6.0.3

Given the following class:
[DataContract]
public class Enumerables
{
[DataMember]
public IEnumerable<Byte> ByteMember { get; set; }
}
And an instance initialized as:
var bytes = new byte[] { ... };
var o = new Enumerables { ByteMember = bytes };
Serialization produces this:
{"ByteMember": "<<base-64-encoded-string>>"}
But this string cannot be deserialized. The error produced is:
Newtonsoft.Json.JsonSerializationException : Error converting value
"vbMBTToz9gyZj6gZuA59rE7ryu3fCfimjVMn8R6A0277Xs9u" to
type 'System.Collections.Generic.IEnumerable`1[System.Byte]'.
Path 'ByteMember', line 1, position 8084.
----> System.ArgumentException : Could not cast or convert from
System.String to System.Collections.Generic.IEnumerable`1[System.Byte].
I don't see this happening for byte[], List<byte> or Collection<byte> properties, which are correctly serialized to and from base-64 strings. And I don't see this happening for any IEnumerable<T> where T is not a byte -- for example, a property of type IEnumerable<int> deserializes to a List<double>, an effective implementation.
How an IEnumerable<byte> gets serialized depends on the concrete type that is assigned to it. If the concrete type is a byte[] then it will get serialized specially as a base-64 encoded string, whereas if it is some other concrete type like a List<byte>, it will be serialized as a normal array of numbers. The same is true of ICollection<byte> and IList<byte>. (DEMO)
On deserialization, Json.Net looks at the types of the member properties of the target class to determine what types of objects to create. When the member property is a concrete type, no problem; it creates an instance of that type and tries to populate it from the JSON. If the member type is an interface, then Json.Net has to make a guess, or throw an error. You could argue that Json.Net should be smart enough to guess that if the member variable is an IEnumerable<byte> and the JSON value is a base-64 encoded string, it should convert the string to a byte[]. But that is not how it is implemented. In fact, the special handling for base-64 encoded byte arrays is only triggered if the member property is byte[] specifically. With no special handling for IEnumerable<byte>, this results in an error because a string can't be assigned directly to an IEnumerable<byte>. Again, the same is true for ICollection<byte> or IList<byte>.
(DEMO)
If you want it to work the same for types implementing IEnumerable<byte> as it does for byte[], you can make a custom JsonConveter like this:
public class EnumerableByteConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<byte>).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
byte[] bytes = ((IEnumerable<byte>)value).ToArray();
writer.WriteValue(Convert.ToBase64String(bytes));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
byte[] bytes = Convert.FromBase64String(reader.Value.ToString());
if (objectType == typeof(byte[]))
{
return bytes;
}
return new List<byte>(bytes);
}
}
To use the converter, create an instance of JsonSerializerSettings and add an instance of the converter to the Converters collection. Then, pass the settings to SerializerObject() and DeserializeObject() methods. For example:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new EnumerableByteConverter());
string json = JsonConvert.SerializeObject(obj, settings);
Here is a working round-trip demo. Note, however, that this converter doesn't (and can't) handle every possible IEnumerable<byte> that might be out there. For example, it won't work with ISet<byte> as currently implemented. If you need support for this or other additional types, you will need to extend the ReadJson method to handle that. I leave this to you.

Spring autoGrowNestedPaths not working

I have a List of objects with nested properties and at the bottom of the hierarchy each object has a CommonsMultipartFile property.
A Folder has a list of Requisites and each of those has a list of Pages
These are the bean definitions, each in its own file:
Page {
private CommonsMultipartFile attributes;
// Getter & Setter
}
Requisite {
private List<Page> pages;
// Other properties and Getters & Setters
}
Folder {
private List<Requisite> requisites;
// Getter & Setter
}
Then I add a Folder object to my modelMap inside a Controller method:
#RequestMapping(value = "loadFiles", method = RequestMethod.GET)
public String initFiles(ModelMap model, HttpServletRequest request) {
Folder folder = new Folder();
folder.setRequisites(requisitesModel.getRequisitesFromDB());
model.addAttribute("folder", folder);
return "loadFiles";
}
At this point the model attribute "folder" has a list of Requisite objects with various properties initialized, however pages (List<Page>) is null in all of them. This approach works fine and allows the user to load a bunch of files and the post request works as expected.
Then I added a method to handle a MaxUploadSizeExceededException and in the resolveException method I copied the behavior of the controller described above. This is to redirect the user to the same form when the total filesize exeeds a given threshold.
Here is the definition of the resolveException method:
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception exception) {
ModelMap model = new ModelMap();
Folder folder = new Folder();
folder.setRequisites(requisitesModel.getRequisitesFromDB());
model.addAttribute("error", "The files exceed the maximum filesize");
model.addAttribute("folder", folder);
return new ModelAndView("loadFiles", model);
}
The problem is that in this one the autoGrowNestedPaths does not work because immediately throws Invalid property 'requisites[0].pages[0]' of bean class [mypackage.Folder]: Cannot access indexed value of property referenced in indexed property path 'pages[0]': returned null.
My understanding is that spring by default autogrows nested paths for all Collection types, even without the use of LazyList or AutopopulatingList. Is my understanding wrong? Do I need to add something else?
I had and solved this problem myself, Jorge.
WebDataBinder does default to auto-grow nested paths; and that includes collections.
But the interesting thing, is that this depends on getting the 'generic type' of the Collection from the property getter method. It uses reflection -- calling Method.getGenericReturnType(), which returns a java.lang.reflect.Type.
If it works, you get a java.lang.reflect.ParameterizedType with the element-type of the collection, to grow; if it doesn't, Spring will get a 'null' element-type & won't auto-grow.
See org.springframework.beans.BeanWrapperImpl.growCollectionIfNecessary().
In my case, Hibernate proxy (subclasses) were found not to have the requisite generic type & method-signature info.. even though this info was on the entity classes (when used directly!)
I de-proxied the entity in my form controller "loadEntity" setup, and was right as rain. (De-proxying is useful & necessary in Hibernate apps sometimes, as are other proxy checks, comparisons, and manipulations.)
Code sample:
public static <T> T deproxy (T obj) {
if (obj == null)
return obj;
if (obj instanceof HibernateProxy) {
// Unwrap Proxy;
// -- loading, if necessary.
HibernateProxy proxy = (HibernateProxy) obj;
LazyInitializer li = proxy.getHibernateLazyInitializer();
return (T) li.getImplementation();
}
return obj;
}
public static boolean isProxy (Object obj) {
if (obj instanceof HibernateProxy)
return true;
return false;
}
public static boolean isSame (Object o1, Object o2) {
if (o1 == o2)
return true;
if (o1 == null || o2 == null)
return false;
Object d1 = deproxy(o1);
Object d2 = deproxy(o2);
if (d1 == d2)
return true;
return false;
}
public static Class getClassWithoutInitializingProxy (Object obj) {
if (obj instanceof HibernateProxy) {
HibernateProxy proxy = (HibernateProxy) obj;
LazyInitializer li = proxy.getHibernateLazyInitializer();
return li.getPersistentClass();
}
// Not a Proxy.
return obj.getClass();
}
Hope this helps guide you to your problem.. give me an upvote even!
From the BeanWrapper javadoc
setAutoGrowNestedPaths
void setAutoGrowNestedPaths(boolean
autoGrowNestedPaths)
Set whether this BeanWrapper should attempt to "auto-grow" a nested
path that contains a null value. If "true", a null path location will
be populated with a default object value and traversed instead of
resulting in a NullValueInNestedPathException. Turning this flag on
also enables auto-growth of collection elements when accessing an
out-of-bounds index.
Default is "false" on a plain BeanWrapper.
So no, BeanWrapperImpl does not autogrow nested path for lists by default (assuming you are using spring 3). Like you mentioned in your post, you can fix this by using an Autopopulating or Lazy list.
You can also use initbinder to exlicitely set autogrowNestedPaths property to true.

Object reference not set to an instance of an object in Session

I am getting an error in this line of code Session.Linq<Employees>() :
" An object reference is required for non-static field,method, or property 'System.Web.UI.Page.Session.get'.
This is my code :
public static object GetData(Dictionary<string, object> tableParams)
{
IQueryable<Employees> Employee = Session.Linq<Employees>();
if (tableParams.ContainsKey("sEcho"))
{
var parser = new DataTableParser<Employees>(tableParams, Employee);
return parser.Parse();
}
return Employee;
}
If I use HttpContext.Current.Session.Linq<Employees>();
then i get:
'System.Web.SessionState.HttpSessionState' does not contain a definition for 'Linq' and no extension method 'Linq' accepting a first argument of type 'System.Web.SessionState.HttpSessionState' could be found '
What do i need to do to get this to work? Am I missing a namespace for Linq with regard to Session?I am using System.Linq and System.Linq.Expression.
I think you're misunderstanding something. What you're trying to do doesn't have anything to do with Linq, at least not in the context of retrieving the object from session.
You need to retrieve the object from session and unbox it:
var list = Session["MyList"] as List<int>;
if (list != null)
{
//the list was found and you can start using it here
}

Resources