My JsonFX serialization code works, but the object that I'm serializing contains a list of polymorphic entities, and they're all deserialized as their base type and not their actual type.
Here's my serialization code:
public static string Serialize(System.Object obj)
{
StringBuilder builder = new StringBuilder();
using (TextWriter textWriter = new StringWriter(builder))
{
JsonWriter writer = new JsonWriter(textWriter);
writer.Write(obj);
return builder.ToString();
}
}
public static T Deserialize<T>(string json)
{
using (TextReader textReader = new StringReader(json))
{
var jsonReader = new JsonReader(textReader);
return jsonReader.Deserialize<T>();
}
}
As you can see it's pretty straightforward. I'm also not decorating my classes with any attributes or anything special to make them serializable. Besides the polymorphic problem, it all just seems to be working properly.
So how can I get my polymorphic types to deserialize properly?.
Thanks.
You need to turn on type hinting. Here's example code (this is relevant to JsonFx v1.4, may or may not work with your version):
StringBuilder result = new StringBuilder(string.Empty);
JsonWriterSettings settings = JsonDataWriter.CreateSettings(true);
settings.TypeHintName = "__type";
JsonWriter writer = new JsonWriter(result, settings);
writer.Write(obj);
return result.ToString();
This will add extra data to your JSON string which looks something like:
"__type": "MyNamespace.MyClass, MyAssembly",
Which means it finds out what derived type it is based on the class name. If you change your class name or namespace name, it won't work anymore. Worse, you can't deserialize from your old JSON text data anymore, unless you mass replace all occurrences of the old class name and replace it with the new.
So you have to be careful with it.
EDIT: Forgot to mention that you have to edit the source code of JsonFx for this to work.
In JsonReader.cs, find the ReadArray method:
Change:
object value = this.Read(arrayItemType, isArrayTypeAHint);
to:
object value = this.Read(null, false);
This will ensure that JsonFx will always attempt to figure out the type of each element in an array/list. If you want this to work for just single variables, well you'd need to do the changes on the appropriate code (haven't tried that).
Related
We are using Couchbase for our Session and for OutputCache.
In this context, how can we cache by a complex object that is being passed to the method using a Custom Model Binder that retrieves a value from the Session?
This is the signature of the method I want to cache with the OutputCache attribute:
[HttpGet]
[OutputCache(CacheProfile = "MyObjectsCache", VaryByParam = "myParam")]
public ActionResult Index([ModelBinder(typeof (CustomVariableSessionModelBinder<MyClass>))] MyClass myParam)
{
Note: The ModelBinder is being used here for reasons beyond me and I cannot change it.
MyClass is a complex object that has an Id. I want to use the Id as the caching identifier.
public class MyClass
{
public int Id{get;set;}
//Other Properties
This is how the object is being retrieved from Session:
var sessionKey = typeof (MyNamespace.MyClass).FullName;
var httpContext = HttpContext.Current;
MyNamespace.MyClass newObject = null;
if (httpContext.Session != null)
{
newObject = httpContext.Session[sessionKey] as MyNamespace.MyClass;
}
Is it possible yo use VaryByParam for this scenario or will I have to use VaryByCustom?
I haven't tested this, but it should work. It's pretty much your only option anyways, though.
In addition to the built in ways to vary, you can vary by "Custom". This will call into a method in Global.asax you'll need to override: GetVaryByCustomString. Importantly for you situation here, this method is passed HttpContext, so you should be able to look into the session. Essentially, the solution will look something like:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
var args = custom.ToLower().Split(';');
var sb = new StringBuilder();
foreach (var arg in args)
{
switch (arg)
{
case "session":
var obj = // get your object from session
// now create some unique string to append
sb.AppendFormat("Session{0}", obj.Id);
}
}
return sb.ToString();
}
This is designed to handle multiple different types of "custom" vary types. For example, if you wanted to vary by "User", which is common, you can merely add a case for that in your switch. The important part is that the string returned by this method is actually what the output cache varies on, so you want that to be unique for the situation. This is why I prefixed the object's id with "Session" here. For example, if you just added the id, let's say 123, and then in another scenario you varied by user and that string was composed of just the user's id, which happened to be 123 as well. It would be the same string to the output cache, and you'd end with some weird results. Just be mindful of what the custom string looks like.
Now, you'd just alter your OutputCache attribute like:
[OutputCache(CacheProfile = "MyObjectsCache", VaryByParam = "myParam", VaryByCustom = "Session")]
Note: to vary by multiple custom things at once, you'd separate them with a ; (based on how the code above works). For example: VaryByCustom = "Session;User"
I'm trying to deserialize two types of json:
{
name: "bob",
worksAt: {
name: "Bobs department store",
location: "downtown"
},
age: 46
}
and
{
name: "Tom",
worksAt: "company:Bobs department store",
age: 27
}
into these objects:
The first way creates two new objects, the second way requests the object from the database based on the contents of a string.
sort of like how jackson mapper can deserialize an arbitrary string into an object, for objects like this:
public class Company{
public String name;
public Employee[] employees
public Company(){}
public Company(String json){
//turn string into object using whatever encoding you want blah blah blah...
}
}
The trouble is I need both. I need it to handle objects and strings. Both could arrive from the same input.
The first think I tried was making a Converter
It says these create a delegate type to pass to the deserializer, but the converter is always applied even when the datatype isn't a string. So that didn't work.
I've also tried a normal deserializer, but I can't find a way to defer to the BeanDeserializer. The beanDeserializer is so complicated that I can't manually instantiate it. I also see no way to defer to a default deserializer in jackson mapper.
Do I have to re-implement jackson mappers deserialization to do this? Is there any way for a deserializer to say "I can't do this, use the default implementation."?
Edit: Some further progress. Based on the Jackson Mapper source code, it looks like you can instatiate bean deserializers like this:
DeserializationConfig config = ctxt.getConfig();
JavaType type = config.constructType(_valueClass);
BeanDescription introspect = config.introspect(type);
JsonDeserializer<Object> beanDeserializer = ctxt.getFactory().createBeanDeserializer(ctxt, type , introspect);
but for some reason all the _beanProperties have the FailingDeserializer set for their _valueDeserializer and the whole thing fails. So I have no idea why that happens...
Have you tried writing a custom deserializer? This gives you the most control on how Jackson deserializes the object. You may be able to try to deserialize one way, and if there's an error, try another way.
Jackson can also handle polymorphic deserialization, though this would require a small change to the json to include type information, and it sounds like your problem constraints might not allow that.
If I understand the problem correctly, I would recommend using JsonNode. You can define a setter in your top-level type like this:
setWorksAt(JsonNode node) {
if (node.getNodeType == JsonNodeType.STRING) {
String name = node.getText();
name = name.substring(name.lastIndexOf(':'));
this.company = new Company(name);
} else if (node.getNodeType == JsonNodeType.OBJECT) {
this.company = mapper.treeToValue(node, Company.class);
}
}
That allows you to handle the two separate worksFor inputs, while still allowing the standard mapper to handle any substructures for the OBJECT case.
With recent versions of Jackson (2.8+ I think, definitely works with 2.9) you can use multiple #JsonCreator and do something like this:
public class Company {
private String name;
private String location;
private Company(String name, String location) {
this.name = name;
this.location = location;
}
private Company(String stringRepresentation) {
// add code here to parse string and extract name and location
}
#JsonCreator
private static Company fromJson(
#JsonProperty("name") String name,
#JsonProperty("location") String location)
{
return new Company(name, location);
}
#JsonCreator
private static Company fromJson(String str) {
return Company(str);
}
}
For testing purposes, I'd like to see all of the properties and their corresponding values of an HttpApplication object (I'm testing some functionality of an HTTPModule). My first thought was to serialize it to XML, then view that or write it to a file.
The problem is, HttpApplication is not a serializable class, so an exception is thrown when I try to serialize. Are there any other techniques, or is it even possible to get a string representation of a non-serializable object? I'd just like to see all of the same properties I get with Intellisense and their values.
I've seen some articles which mention Reflection, but I haven't found anything that suggests it would work for my scenario.
UPDATE:
After getting a couple responses, it looks like I'll need to use Reflection. Here is the code I'm using:
Dim sProps As New StringBuilder
For Each p As System.Reflection.PropertyInfo In oHttpApp.GetType().GetProperties()
If p.CanRead Then
sProps.AppendLine(p.Name & ": " & p.GetValue(oHttpApp, Nothing))
End If
Next
On my AppendLine statement, an exception is thrown right away:
System.InvalidCastException: Operator '&' is not defined for string
"Context: " and type 'HttpContext'. at
Microsoft.VisualBasic.CompilerServices.Operators.InvokeObjectUserDefinedOperator(UserDefinedOperator
Op, Object[] Arguments) at
Microsoft.VisualBasic.CompilerServices.Operators.InvokeUserDefinedOperator(UserDefinedOperator
Op, Object[] Arguments) at
Microsoft.VisualBasic.CompilerServices.Operators.ConcatenateObject(Object
Left, Object Right)
#granadaCoder, you mentioned that I'll need to know how "deep" to go, I'm wondering if this is the problem. In the error above, Context is an complex object so I would need to drill into that and get its individual properties, correct? Do you know how I might be able to do that - or would it be as simple as calling GetProperties again on p inside my loop?
Sounds like a good use case for reflection--
How to iterate through each property of a custom vb.net object?
You could iterate over all the object's properties and create your own XML/JSON view of them.
Update--
Here is c# code of how i turn any object to a dictionary (which would work for your use case)
public static Dictionary<string,string> ToDictionary<T>(this T me, string prefix=null) where T:class
{
Dictionary<string, string> res = new Dictionary<string, string>();
if (me == null) return res;
var bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.GetField;
var properties = me.GetType().GetProperties(bindingFlags)
.Where(i => i.CanRead
);
foreach (var i in properties)
{
var val = i.GetValue(me, null);
var str = "";
if (val != null)
str = val.ToString();
res[string.Format("{0}{1}", prefix, i.Name)] = str;
}
return res;
}
Some objects are not meant to be serializable. Take an IDataReader for an example.
You're gonna have to go with reflection. And "pluck off" the properties that are readable.
Here's some get-started code.
private void ReadSomeProperties( SomeNonSerializableObject myObject )
{
foreach( PropertyInfo pi in myObject.GetType( ).GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty) )
{
//pi.Name
//pi.GetValue( myObject, null )
//don't forget , some properties may only have "setters", look at PropertyInfo.CanRead
}
}
Of course, when the property is a complex object (not a scalar), then you have to figure out how "deep" you want to go digging.
Here is the code trying from compact framework to get http service..
List<Table> tables;
using (Stream r = response.GetResponseStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(Table),"http://schemas.datacontract.org/2004/07/");
tables=(List<Table>) serializer.Deserialize(r);
}
response.Close();
It fails with {"There is an error in XML document (1, 2)."}
{"<ArrayOfTable xmlns='http://schemas.datacontract.org/2004/07/WpfApplication1.Data.Model'> was not expected."}
Table namespace is the same...
I dont know whats wrong there...
UPDATE
Problem was that i had typeof(Table) not typeof(List<Table>) which works partially.. No error but created tables values are null!
The second parameter on the XmlSerializer constructor works for both serializing and deserializing. So, on the second parameter (the namespace) should be the same to the one being received. So you'll end up having:
XmlSerializer serializer = new XmlSerializer(typeof(Table),"http://schemas.datacontract.org/2004/07/WpfApplication1.Data.Model")
Note the "WpfApplication1.Data.Model" at the end of the namespace string.
One way to get rid of the namespace thing. Is to specify on your model class (Table) that it should not use a namespace:
[DataContract(Namespace = "")]
public class Table { ... }
That way you don't need to specify the namespace for deserialization.
Hope it helps!
Not sure if this will help, but we had a similar issue. Instead of decorating thousands of data elements with DataContract/DataMember attributes and using the (default) DataContractSerializer, we found that if our WCF service used the XmlSerializerFormat instead, we could easily deserialize our objects.
[System.ServiceModel.ServiceContract]
public interface IRestService
{
[System.ServiceModel.OperationContract]
// Added this attribute to use XmlSerializer instead of DataContractSerializer
[System.ServiceModel.XmlSerializerFormat(
Style=System.ServiceModel.OperationFormatStyle.Document)]
[System.ServiceModel.Web.WebGet(
ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Xml,
UriTemplate = "xml/objects/{myObjectIdentifier}")]
MyObject GetMyObject(int myObjectIdentifier);
}
This is how we're deserializing the objects:
public static T DeserializeTypedObjectFromXmlString<T>(string input)
{
T result;
try
{
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (System.IO.TextReader textReader = new System.IO.StringReader(input))
{
result = (T)xs.Deserialize(textReader);
}
}
catch
{
throw;
}
return result;
}
Instead of returning a List return an object that has a single property of List.
I have created a User Defined Type in .Net 3.5 as per my blog entry at :
http://jwsadlerdesign.blogspot.com/2009/04/this-is-how-you-register.html
This works fine when using SQL with technologies like nHibernate.
However, when I try to map my LinQ to SQL class to use this UDT (with attribute defintions not XML), and I setup the property as the enumeration. I cannot get LinQ to map to this type. I have tried Image, Binary, varchar and integer all of which seem to issue Invalid Cast errors.
In particular I get the error 'Unable to cast object of type 'ISTD.InstallManager.Common.Classes.SQLUDTTargetType' to type 'System.Byte[]' any ideas or help would be much appreciated.
James.
UPDATE: I ran into this myself recently and found that the previous solution wasn't quite complete. Despite what all of the documentation says, it is possible to do this, but somewhat painful.
The first step, for your own convenience, is to implement some conversion operators:
public class MyUDT : INullable, IBinarySerialize
{
// Class implementation would go here
// ...
public static explicit operator MyUDT(byte[] data)
{
using (MemoryStream stream = new MemoryStream(data))
{
using (BinaryReader reader = new BinaryReader(stream))
{
MyUDT result = new MyUDT();
result.Read(reader);
return result;
}
}
}
public static explicit operator byte[](MyUDT x)
{
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
{
x.Write(writer);
}
return ms.ToArray();
}
}
}
Linq to SQL will still flat-out refuse to give you the UDT field, no matter how you declare the property. So you have to give it a binary field instead. You don't need a stored procedure or any custom SQL for this, just add a computed column to your table:
ALTER TABLE MyTable
ADD UDTField_Data AS CAST(UDTField AS varbinary(len))
Where len is whatever your UDT defines in the MaxByteSize attribute.
Now you can finally get access to the column data. You might be tempted to use your UDT as the return type of the new property, thinking that Linq to SQL will find your conversion operator and automatically convert from the byte array; don't bother. Linq to SQL will decide that it's actually a serialized .NET object and spit out a message to the effect of "input stream is not a valid binary format." Instead, you need another layer of indirection:
private MyUDT udtField;
[Column(Name = "UDTField_Data", DbType = "varbinary(len)")]
private byte[] UdtFieldData
{
get { return (byte[])udtField; }
set { udtField = (MyUDT)value; }
}
public MyUDT UdtProperty
{
get { return udtField; }
set { udtField = value; }
}
A few notes to make it clear what's going on here:
The actual field data (udtField) is declared as the UDT itself, not a byte array. The reason for this is that we only want the conversion to happen when loading from or saving to the database. If you had to convert the byte array to the UDT every time you accessed it, it would not only hurt performance, but it would cause inconsistencies if the UDT declares any mutable fields.
The raw byte[] property (UdtFieldData) is declared private, so consumers only see the UDT itself. Linq to SQL will still read it as long as it has the [Column] attribute.
The UdtFieldData property does not declare a storage property. This is critical; if you try to use the UDT field as the storage property, you'll just get the same type conversion error.
Finally, the UdtProperty property is how consumers actually get to access the data. To them it looks like any other property.
It's unfortunate that you have to jump through so many hoops to get this to work, but it does work. You'll probably have difficulties doing this kind of massaging through the Linq surface designer, which is just one of several reasons why I don't use it; better to write the classes yourself and use SqlMetal to help you along if necessary.