【问题标题】:JSON serialization and deserialization for embedded strictly-typed .NET objects嵌入式严格类型的 .NET 对象的 JSON 序列化和反序列化
【发布时间】:2013-11-28 13:34:43
【问题描述】:

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


我为 Oracle DB 编写了 CRUD 接口,并使用 DNF 格式子句为它加入了搜索过滤器。 下一步,我编写了一个函数来验证用户数据(这不是特殊符号的转义以避免 SQL 注入,而是字段名称的验证)。 在这个函数中,我使用了一个像字典一样的哈希表。我希望将其序列化为 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  
}

如您所见,结果包含较少的字段(仅包含我在查询中包含的那些)和转换为字符串的参数值(字节 [])。可以为 OracleParameterSerializer 类编写反序列化方法 - 但我看不出重点,因为我的自定义转换器无论如何都不会自动加入。 也许有办法“修补”标准 OracleParameter,添加所需的属性或编写一个类 SerializableOracleParameter,从 System.Data.Common.DbParameter 继承它,以及转换方法,如 ConvertMethod (SerializableOracleParameter) -> OracleParameter。但这样做需要一个充分的理由。

因此,我决定保留一切原样并使用 JavaScriptSerializer 来解决我最初的问题。 (下面是我的灵魂部分的借口/口头禅,他有完美主义偏好,哈哈。)

  1. 我的应用程序在这一点上具有良好的性能 片刻。
  2. 我已经在某些地方使用了 JavaScriptSerializer 我的代码的一部分(因为 JSON.NET 绝对不适合。)。
  3. 处理 JavaScriptSerializer 很简单。

我希望这些信息有用。

【问题讨论】:

  • OracleParameter [serializable] 还是[datacontract]
  • 我不知道 - 可能不是:OracleParameter 是一个“嵌入式”类:System.Data.OracleClient.OracleParameter - 我没有创建它。如何将这些属性附加到 OracleParameter?
  • 不可能。一旦一个类被编译成一个程序集,属性就会被烧入其中,您既不能删除,也不能添加,也不能更改它们。但是可以在 JSON.Net 中为该类注册一个外部序列化策略,我现在正在检查它。

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


【解决方案1】:

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

根据您的观察,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 库允许您创建一个特殊的“帮助程序类”并注册它以处理您选择的某些类型的(反)序列化。您可以为 OracleParam 创建和注册这样的帮助程序,然后“手动”处理它:编写一些代码来检查里面的内容并将其转储到存储中(或从存储中读取并放入 OracleParam 对象)。

编辑:

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

【讨论】:

  • 我发现的另一篇文章:run80.net/… - 但我不确定它有多大(无)帮助。
  • >*...所以,JSON.Net 很可能也有一些“已知类型”。例如,看这里:stackoverflow.com/a/6495299/717732 ... 也许只是你错过了?* 这部分看起来很有希望 - 我稍后会尝试使用它,很酷的提示!我还找到了一个比较有代表性的link in SO。我玩过 JsonSerializerSettings - 但我记得我收到了一些错误。无论如何,非常感谢。
【解决方案2】:

您可能想查看我添加到这个的单元测试(查找:“$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”伪键,以及如何反序列化(通过这种可能替代微软的 JavaScriptSerializer)而不需要您在任何地方添加自定义属性(这是不可能的,在 Oracle 的单独编译的模块上,您没有创作)。

'希望这会有所帮助,

【讨论】:

    猜你喜欢
    • 2011-08-14
    • 2019-12-08
    • 2016-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    相关资源
    最近更新 更多