嵌入式严格类型.NET对象的JSON序列化和反序列化

时间:2013-11-28 13:34:43

标签: c# .net json serialization json.net

我使用:C#,. NET 2.0和JSON.NET v5.08.16617。


我为Oracle DB编写了CRUD接口,并使用DNF格式子句加入了搜索过滤器。 下一步,我写了一个函数来验证用户数据(这不是关于特殊符号的转义,以避免SQL注入,而是验证字段的名称)。 在这个函数中,我使用了像Dictionary这样的哈希表。我希望将它序列化为JSON格式并将其放入资源文件中 - 目的是在需要时访问它 有时会做一些更改,而无需重新编译整个项目。

为此,我使用了JSON.NET库并发现了一个问题:一些对象没有使用JSON.NET进行序列化/反序列化,例如 - OracleParameter。

我的测试代码:

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);

结果(不好):

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

作为一种临时快速的解决方案,我使用了JavaScriptSerializer。

我的测试代码:

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

结果(很棒):

{
    "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
    }
}

反序列化也很有趣:

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

这个解决方案对我来说很稳定且非常快,但我在JavaScriptSerializer上有一个额外的链接。


所以我的问题是:如何使用JSON.NET库而不是JavaScriptSerializer获得正确的结果? (我正在搜索有关此问题的信息的课程(SO [json.net]JSON.NET documentationgoogle),但我没有找到任何有用的信息。)


已更新

所以,我检查了使用TypeNameHandling参数(All,Arrays,Auto,None,Objects)的选项 - 它对我不起作用。

例如,像

这样的代码
var vSettings = new JsonSerializerSettings();
vSettings.TypeNameHandling = TypeNameHandling.Objects;
vJsonStr = JsonConvert.SerializeObject(vDictionary, Formatting.Indented, vSettings);

只将参数$ type添加到序列化字符串:

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

好的,我已经检查了有关自定义转换器的主题。我发现了几篇howto-format的文章,我也用源JSON.NET检查过:它包含一个新转换器的模板 - 一个抽象类CustomCreationConverter (代码的其余部分,虽然结构良好,评论很好,但对我来说需要花费很多时间来理解)。

然而,我写了一个小型原型来测试我的假设:

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);
    }
}

这里的主要问题是为我要序列化的类添加了一个属性:

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

但是OracleParameter - 已经组装好了,我不能改变它的属性。但是,我找到了一个使用System.ComponentModel的解决方案(在运行时添加了属性):

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]

虽然它不起作用(即,添加了属性 - 但序列化失败),我看到OracleParameter有一个属性[System.SerializableAttribute] - 显然,它允许标准的JavaScriptSerializer序列化这个类。


好的,我已经尝试过直接序列化(我序列化OracleParameter“p2”):

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

它有类似的东西:

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

如您所见,结果包含较少的字段(仅包含在查询中的字段)和参数Value(byte [])转换为字符串。可以为OracleParameterSerializer类编写反序列化方法 - 但我没有看到这一点,因为无论如何我的自定义转换器都没有自动连接。 也许有一种方法可以“修补”标准的OracleParameter,添加所需的属性或编写一个类SerializableOracleParameter,从System.Data.Common.DbParameter继承它,以及转换方法就像一个 ConvertMethod(SerializableOracleParameter) - &gt;的OracleParameter。但是做这样的事情需要一个很好的理由。

因此,我决定将所有内容保留原样并使用JavaScriptSerializer来解决原始问题。 (下面是我灵魂部分的借口/口头禅,有完美主义偏好,HA-HA。)

  1. 我的应用程序此时表现不错 时刻。
  2. 我已经在某些方面使用过JavaScriptSerializer 我的代码的一部分(因为JSON.NET明确地不适合。)。
  3. 处理JavaScriptSerializer很简单。
  4. 我希望这些信息有用。

2 个答案:

答案 0 :(得分:0)

[Serializable]ISerializable[DataContract]等的要求在各种序列化程序中非常常见。一种常见的行为是序列化/反序列化用这些标记的所有内容,并注意到任何未标记的内容。未标记为可序列化的对象通常被视为“具有未知的序列化过程”,因此不可序列化,因此(de)序列化在这种情况下失败 - 它根本不知道如何处理它们。

根据您的观察,JSON.Net似乎行为不同 - 它只是跳过未知对象。这是一个遗憾,因为如果它抛出一个异常,那么它可以给你一个暗示在异常消息中做什么。例如,通常的解决方案是将有问题的类型添加到序列化框架管理的某些“使用典型约定可安全序列化的众所周知的类型”列表中。我的意思是,简单地说:

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

通常就足够了。

JSON.Net似乎很乐意序列化一个未归属的类。至少这篇文章声称如此:http://alexandrebrisebois.wordpress.com/2012/06/24/using-json-net-to-serialize-objects/在本文中,您会看到仅在提供文本的情况下调用Deserialize,而且还有确切的对象类型(TType参数)。可能这会强制库序列化/反序列化那个未归属的对象,因为你明确地给出了要处理的类型。

所以,很可能JSON.Net也有一些“已知类型”,但我无法找到它。

另一件事,请看这里:https://stackoverflow.com/a/6495299/717732没有任何对象被归因,TypeNameHandling迫使库明确地写出JSON数据包中的所有类型名,所以后来它知道要反序列化的内容他们是。也许只是你失踪了?

如果您仍然不幸,那么您可以在OracleParameter课程外部提供序列化程序。请参阅此处:http://james.newtonking.com/json/help/index.html?topic=html/SerializeSerializationBinder.htm JSON.Net库允许您创建一个特殊的“帮助程序类”并将其注册以处理(de)某些类型的序列化。您可以为OracleParam创建并注册这样的帮助程序,然后“手动”处理它:编写一些代码来检查内部的内容并将其转储到商店(或从商店读取并放入OracleParam对象)。 / p>

编辑:

您甚至可以直接向DeserializeObject method提供自定义转换器。这是converters的参考。因此,您可以创建CustomCreationConverter<OracleParameter>实现,并在需要时将其传递给(de)序列化程序。

答案 1 :(得分:0)

您可能想查看我添加到此单元的单元测试(查找:“$ type”):

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

这是我所暗示的单元测试摘录,非常类似于你的情况(以模型名称为模):

        // 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
        );

因此,在序列化期间让JSON.NET在伪键“$ type”中发出对象类型信息(即完全限定类型名称)时,我没有任何问题,这要归功于上述:

new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }

现在,我无法谈论如何正确地反序列化你的OracleParameter,因为我不能自己测试它,但上面的单元测试显示了如何通过放置类型信息提示来使JSON.NET序列化进入“$ type”伪键,以及如何反序列化(通过这可能替代Microsoft的JavaScriptSerializer),而不需要在任何地方添加自定义属性(这是不可能的,在你没有编写的Oracle单独编译的模块上)。

'希望这有帮助,