3

I work with: C#, .NET 2.0 and JSON.NET v5.08.16617.


I wrote CRUD-interface to Oracle DB and joined search-filter for it with the DNF-format clauses. Next step, I wrote a function to validate user data ( This is not about escape of special symbols to avoid SQL injections, but the validation of field's names ) . In this function I used a hash-table like a Dictionary. I hoped to serialize it into JSON format and put it into the resource file - with the aim of getting access to it if I need and sometimes make some changes without recompiling the whole project again.

For this purpose I used JSON.NET library and saw a problem: some objects are not serialized / deserialized with JSON.NET, for example - OracleParameter.

My test code:

string vJsonStr;
Dictionary<string, OracleParameter> vDictionary = new Dictionary<string, OracleParameter> ();
OracleParameter vOp;

vOp = new OracleParameter();
vOp.DbType = DbType.String;
vOp.OracleType = OracleType.VarChar;
vOp.Value = "qwerty";
vOp.Direction = ParameterDirection.InputOutput;
vDictionary.Add("p1", vOp);

vOp = new OracleParameter();                
vOp.OracleType = OracleType.Clob;
vOp.Value = new byte[3] { 1, 2, 3 };
vOp.Direction = ParameterDirection.Input;
vDictionary.Add("p2", vOp);               

vJsonStr = JsonConvert.SerializeObject(vDictionary);

And the result (bad):

{
    "p1": "",
    "p2": ""
}

As a temporary and quick solution I used the JavaScriptSerializer.

My test code:

JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
vJsonStr = javaScriptSerializer.Serialize(vDictionary);

And the result (great):

{
    "p1": {
        "DbType": 0,
        "OracleType": 22,
        "ParameterName": "",
        "Precision": 0,
        "Scale": 0,
        "Value": "qwerty",
        "Direction": 3,
        "IsNullable": false,
        "Offset": 0,
        "Size": 6,
        "SourceColumn": "",
        "SourceColumnNullMapping": false,
        "SourceVersion": 512
    },
    "p2": {
        "DbType": 0,
        "OracleType": 4,
        "ParameterName": "",
        "Precision": 0,
        "Scale": 0,
        "Value": [
            1,
            2,
            3
        ],
        "Direction": 1,
        "IsNullable": false,
        "Offset": 0,
        "Size": 3,
        "SourceColumn": "",
        "SourceColumnNullMapping": false,
        "SourceVersion": 512
    }
}

Deserialization is worked funny too:

Dictionary<string, OracleParameter> test2 = javaScriptSerializer.Deserialize<Dictionary<string, OracleParameter>>(vJsonStr);

This solution is stable and very fast for me, but I got an extra link on JavaScriptSerializer.


So my question is: how can I get the correct result using the JSON.NET library instead of JavaScriptSerializer? (A course I was searching the information about this issue (SO [json.net] and JSON.NET documentation and google), but I did not find anything useful.)


UPDATED

And so, I'm checked the option to use TypeNameHandling parameter (All, Arrays, Auto, None, Objects) - it does not work for me.

For example, code like

var vSettings = new JsonSerializerSettings();
vSettings.TypeNameHandling = TypeNameHandling.Objects;
vJsonStr = JsonConvert.SerializeObject(vDictionary, Formatting.Indented, vSettings);

only adds parameter $type to serialized string:

{
"$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Data.OracleClient.OracleParameter, System.Data.OracleClient]], mscorlib",
"p1": "",
"p2": ""
}

Ok, I've checked theme about the custom converters. I've found several articles in howto-format and I checked with source JSON.NET also: it contains a template for new converters - an abstract class CustomCreationConverter ( The rest of the code, though a structured and well commented, but for me it takes much time to understand).

Nevertheless, I wrote a small prototype to test my assumptions:

public class OracleParameterSerializer: JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var vOp = value as OracleParameter; 

        writer.WriteStartObject();

        writer.WritePropertyName("DbType");             
        serializer.Serialize(writer, vOp.DbType);

        writer.WritePropertyName("Direction");
        serializer.Serialize(writer, vOp.Direction);

        writer.WritePropertyName("IsNullable");
        serializer.Serialize(writer, vOp.IsNullable);

        writer.WritePropertyName("Offset");
        serializer.Serialize(writer, vOp.Offset);

        writer.WritePropertyName("OracleType");
        serializer.Serialize(writer, vOp.OracleType);

        writer.WritePropertyName("ParameterName");
        serializer.Serialize(writer, vOp.ParameterName);

        writer.WritePropertyName("Size");
        serializer.Serialize(writer, vOp.Size);

        writer.WritePropertyName("SourceColumn");
        serializer.Serialize(writer, vOp.SourceColumn);

        writer.WritePropertyName("SourceColumnNullMapping");
        serializer.Serialize(writer, vOp.SourceColumnNullMapping);

        writer.WritePropertyName("SourceVersion");
        serializer.Serialize(writer, vOp.SourceVersion);

        writer.WritePropertyName("Value");
        serializer.Serialize(writer, vOp.Value);

        writer.WriteEndObject();                
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(OracleParameter).IsAssignableFrom(objectType);
    }
}

The main problem here is the addition of an attribute to the class that I'm want to serialize:

[JsonConverter(typeof(OracleParameterSerializer))]
...Class OracleParameter...

But OracleParameter - already assembled and I can not to change its attributes. However, I found one solution with using System.ComponentModel (Attributes are added at runtime):

var vAttrs1 = TypeDescriptor.GetAttributes(typeof(OracleParameter));
TypeDescriptor.AddAttributes(typeof(OracleParameter), new Attribute[] { new JsonConverterAttribute(typeof(OracleParameterSerializer)) }); // JsonConverter(typeof(OracleParameterSerializer)) - it's not working, I don't know why.
var vAttrs2 = TypeDescriptor.GetAttributes(typeof(OracleParameter)); // Added [Newtonsoft.Json.JsonConverterAttribute]

Although it does not work (I.e., the attribute is added - but serialization fails), I've seen that OracleParameter has an attribute [System.SerializableAttribute] - apparently, it allows the standard JavaScriptSerializer to serialize this class.


Okay, I've tried the directly serialization ( I serialize OracleParameter "p2" ) :

vJsonStr = JsonConvert.SerializeObject(vOp, Formatting.Indented, new OracleParameterSerializer());

it gets something like:

{
  "DbType": 0,
  "OracleType": 4,
  "ParameterName": "",  
  "Value": "AQID",
  "Direction": 1,
  "IsNullable": false,
  "Offset": 0,
  "Size": 3,
  "SourceColumn": "",
  "SourceColumnNullMapping": false,
  "SourceVersion": 512  
}

As you can see, the result contains the less fields ( only those that I have included in the query ) and parameter Value (byte []) converted to string. It's possible to write a deserialize method for OracleParameterSerializer class - but I don't see the point, because my custom converter is not joined automatically anyway. Perhaps there is a way "to patch" standard OracleParameter, adding the required attributes or write a class SerializableOracleParameter, inheriting it from System.Data.Common.DbParameter, as well as the converting method like a ConvertMethod (SerializableOracleParameter) -> OracleParameter. But it needs a good reason to do something like that.

Thus, I decided to leave everything as it is and use the JavaScriptSerializer for my original problem. ( Below is an excuse/mantra for a my soul-part, who has perfectionistic preferences, HA-HA. )

  1. My application has a good performance at this point at the moment.
  2. I have already been using JavaScriptSerializer in some part of my code (Since JSON.NET categorically does not fit.).
  3. Handling JavaScriptSerializer is trivial.

I hope this information was useful.

Community
  • 1
  • 1
LessWrong
  • 63
  • 1
  • 7
  • 1
    Is the `OracleParameter` `[serializable]` or a `[datacontract]` in any way? – quetzalcoatl Nov 28 '13 at 13:39
  • I don't know - probably not: OracleParameter is a "embedded" class: [System.Data.OracleClient.OracleParameter](http://msdn.microsoft.com/en-us/library/system.data.oracleclient.oracleparameter(v=vs.110).aspx) - I didn't create it. How I can attach these attributes to OracleParameter? – LessWrong Nov 28 '13 at 13:59
  • It is not possible. Once a class has been compiled down into an assembly, the attributes are burnt into it and you cannot neither remove, nor add, nor change them. But it may be possible to register in JSON.Net an external serialization policy to that class, I'm checking it right now. – quetzalcoatl Nov 28 '13 at 14:15

2 Answers2

0

The requirement for [Serializable], ISerializable or [DataContract] etc is quite common amongst various serializers. A common behavior is to serialize/deserialize everything marked with these, and throw upon noticing anything not-marked. Objects not marked as serializable are usually perceived as "having unknown serialization process", thus, not serializable, so (de)serialization fails in such cases - it simply does not know how to process them.

From your observations, JSON.Net seems to behave differently - it simply skips unknown objects. That's a pity, because if it threw an exception, then it could leave you a hint what to do in the exception's message. For example, the usual solution is to add the problematic type to some "well-known types that are safe to serialize with typical conventions" list managed by the serialization framework. I mean, simply something like:

MySerializationLibrary.KnownTypes.Add( typeof(MyUnattributedClass) );

is usually enough.

The JSON.Net seems to happily serialize a unattributed classes. At least this article claims so: http://alexandrebrisebois.wordpress.com/2012/06/24/using-json-net-to-serialize-objects/ In this article you see a Deserialize being called on only with the text being supplied, but also the exact object type (the TType parameter). Probably this forces the library to serialize/deserialize that unattributed object, because you explicitely gave that type as to be processed.

So, most probably JSON.Net has some "known types" too, but I was not able to find it.

Another thing, please look here: https://stackoverflow.com/a/6495299/717732 None of the objects is attributed, byt the TypeNameHandling forces the library to explicitely write all the typenames in the JSON packets, so later it knows what to deserialize them as. Maybe it's just that you were missing?

If you are still out of luck, then you can provide a serializer externally for that OracleParameter class. See here: http://james.newtonking.com/json/help/index.html?topic=html/SerializeSerializationBinder.htm The JSON.Net library allows you to create a special "helper class" and register it to handle (de)serialization of some types of your choice. You could create and register such helper for that OracleParam and then handle it "manually": write some code that would check what's inside and dump it to the store (or read it from store and put into OracleParam object).

EDIT:

You can even provide custom converters directly to the DeserializeObject method. Here's the reference for converters. So, you could create CustomCreationConverter<OracleParameter> implementation and pass it to the (de)serializer whenever needed.

Community
  • 1
  • 1
quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • One more article I've found: http://www.run80.net/json-adventures-json-net-datacontractjsonserializer-and-a-gun/181 - but I'm not certain how (un)helpful could it be. – quetzalcoatl Nov 28 '13 at 14:35
  • >*...So, most probably JSON.Net has some "known types" too. For example, look here: http://stackoverflow.com/a/6495299/717732 ... Maybe it's just that you were missing?* This part looks promising - I'll try use it later, cool tip! I also found a more representative [link in SO](http://stackoverflow.com/a/2390801). I've played with JsonSerializerSettings - but I'm received some errors as I'm remember. Thank you very much anyway. – LessWrong Nov 28 '13 at 15:02
0

You may want to check out the unit test I added to this one (lookup : "$type"):

https://github.com/ysharplanguage/FastJsonParser/blob/master/JsonTest/Program.cs

Here's the unit test excerpt I'm alluding to, quite similar to your case (modulo the type names):

        // Support for JSON.NET's "$type" pseudo-key (in addition to ServiceStack's "__type"):
        Person jsonNetPerson = new Person { Id = 123, Name = "Foo", Scores = new[] { 100, 200, 300 } };

        // (Expected serialized form shown in next comment)
        string jsonNetString = JsonConvert.SerializeObject(jsonNetPerson, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
        // => '{"$type":"Test.Program+Person, Test","Id":123,"Name":"Foo","Status":0,"Address":null,"Scores":[100,200,300],"Data":null,"History":null}'

        // (Note the Parse<object>(...))
        object restoredObject = UnitTest(jsonNetString, s => new JsonParser().Parse<object>(jsonNetString));
        System.Diagnostics.Debug.Assert
        (
            restoredObject is Person &&
            ((Person)restoredObject).Name == "Foo" &&
            ((IList<int>)((Person)restoredObject).Scores).Count == 3 &&
            ((IList<int>)((Person)restoredObject).Scores)[2] == 300
        );

So, I didn't have any issue with having JSON.NET emit the object type info (i.e., the fully qualified type name) in that pseudo-key "$type" during serialization, thanks to the above:

new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }

Now, I can't speak about what it should take to deserialize properly your OracleParameter, because I can't test it by myself, but this unit test above shows how to have JSON.NET serialize by putting that type info hint into the "$type" pseudo-key, and how to deserialize (by this possible replacement for Microsoft's JavaScriptSerializer) without requiring you to add custom attributes anywhere (which isn't possible, on an Oracle's separately compiled module that you didn't author).

'Hope this helps,

YSharp
  • 1,066
  • 9
  • 8