【问题标题】:C# data contract for complex JSON object复杂 JSON 对象的 C# 数据协定
【发布时间】:2017-02-05 11:04:11
【问题描述】:

这可能是一件非常简单的事情,我到处寻找并尝试了我能想到的一切。因此,如果这是一个简单的搜索并且我只是在寻找错误的东西,我深表歉意。我也是数据合同的新手,有点 JSON,所以这可能不是那么复杂。

我正在创建一个 API 来提取 JSON 并将其存储在我们的数据库中。 JSON 将如下所示:

{
"appname" : "MyApp",
"key" : "Test123",
"data" :[
  { "data1" : "10551296", "data2" : "TrainingIns", "data3" : "Completed"}
  ,
  { "connectorType" : "webserver-to-appserver", "sourceUri" : "data4", "destinationUri" : "data5", "rails" : "N", "data6" : "null" }
  ,
  { "groupId" : "group1", "failbackAction" : "null", "normal" : "null", "failoverAction" : "null", "failbackAction" : "null", "failoverAction" : "null", "artifactId" : "mywebserver", "normalState" : "null" }
  ,
  { "instanceId" : "10551296abc" }]
,
"updateId" : "MyID",
"updateTS" : "30-AUG-16 05.56.24.000000000 AM" ,
"creationUser" : "APICall"
}

其中的“数据”字段将是一个包含可变数量 JSON 对象的数组。我遇到的问题源于未在“数据”对象中获取数据或完全未定义。

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name="data",IsRequired = true)]
    public List<JsonValue> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

我已经收集到我可能需要某种类型的收集?我已经尝试了所有我能找到的东西,但我不知道我应该如何定义“数据”的数据成员。 当我这样做时,上面的合同给了我空数组:

string x = JsonConvert.SerializeObject(collection.data);

我可以获取所有其他字段,只需将“数据”字段转换为字符串。

希望这是足够的信息。提前感谢您的帮助!

【问题讨论】:

  • 1) 可以分享JsonValue 类吗? 2) 您的 JSON 具有重复的属性名称:"failoverAction"。根据standard当对象中的名称不唯一时,接收此类对象的软件的行为是不可预测的。你确定你需要这个吗?
  • JsonValue 内置在 System.Json 命名空间中。我实际上只是编辑了一些数据,因为它与工作相关,而且恰好是相同的。所以通常他们不会。
  • 好的 - 但您实际上不是在使用 Json.NET 吗?而且我相信System.Json 已被弃用,请参阅nuget.org/packages/System.Json。您确定要使用它吗?
  • 啊,是的,我正在使用 Json.NET。几天前我拿起了这个项目,所以我还在想办法。 JsonValue 来自我不断尝试的事情,因为我无法弄清楚读取“数据”字段需要什么数据类型。

标签: c# json datacontract


【解决方案1】:

使用Json2CSharp.com 确保一切正确:

 public class Datum
{
public string data1 { get; set; }
public string data2 { get; set; }
public string data3 { get; set; }
public string connectorType { get; set; }
public string sourceUri { get; set; }
public string destinationUri { get; set; }
public string rails { get; set; }
public string data6 { get; set; }
public string groupId { get; set; }
public string failbackAction { get; set; }
public string normal { get; set; }
public string failoverAction { get; set; }
public string artifactId { get; set; }
public string normalState { get; set; }
public string instanceId { get; set; }
}

public class RootObject
{
public string appname { get; set; }
public string key { get; set; }
public List<Datum> data { get; set; }
public string updateId { get; set; }
public string updateTS { get; set; }
public string creationUser { get; set; }
}

【讨论】:

  • 我知道这是怎么回事,但问题是“数据”可以有可变数量的参数。我猜这意味着我不应该使用数据合同吗?这就是为什么我认为在没有预定义元素数量的情况下将其设为某种 Collection 是可行的。
【解决方案2】:

在正常情况下,您可以将data 属性定义为List&lt;Dictionary&lt;string, string&gt;&gt;,如下所示:

    [DataMember(Name = "data", IsRequired = true)]
    public List<Dictionary<string, string>> data { get; set; }

然后您将能够使用 Json.NET 成功对其进行序列化和反序列化。不幸的是,您的一个数据对象有重复键

  {
     "groupId":"group1",
     "failbackAction":"null",
     "normal":"null",
     "failoverAction":"null",
     "failbackAction":"null",
     "failoverAction":"null",
     "artifactId":"mywebserver",
     "normalState":"null"
  },

JSON standard 不建议使用重复的密钥,其中指出:

当对象中的名称不唯一时,接收此类对象的软件的行为是不可预测的。

另外,c#字典当然不支持重复键,数据契约序列化也不支持重复属性名。

但是,可以使用 Json.NET 的 JsonReader 读取具有重复键的 JSON 对象,并创建一个 custom JsonConverter 来处理重复键。

首先,定义以下类来替换JsonValueJsonValue 是一个特定于 Silverlight 的类,它的使用一直是 deprecated in overall .Net

[JsonConverter(typeof(JsonValueListConverter))]
public sealed class JsonValueList
{
    public JsonValueList()
    {
        this.Values = new List<KeyValuePair<string, string>>();
    }

    public List<KeyValuePair<string, string>> Values { get; private set; }
}

class JsonValueListConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(JsonValueList).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var jsonValue = (existingValue as JsonValueList ?? new JsonValueList());
        if (reader.TokenType != JsonToken.StartObject)
            throw new JsonSerializationException("Invalid reader.TokenType " + reader.TokenType);
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.PropertyName:
                    {
                        var key = reader.Value.ToString();
                        if (!reader.Read())
                            throw new JsonSerializationException(string.Format("Missing value at path: {0}", reader.Path));
                        var value = serializer.Deserialize<string>(reader);
                        jsonValue.Values.Add(new KeyValuePair<string, string>(key, value));
                    }
                    break;
                case JsonToken.EndObject:
                    return jsonValue;
                default:
                    throw new JsonSerializationException(string.Format("Unknown token {0} at path: {1} ", reader.TokenType, reader.Path));
            }
        }
        throw new JsonSerializationException(string.Format("Unclosed object at path: {0}", reader.Path));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var jsonValue = (JsonValueList)value;
        writer.WriteStartObject();
        foreach (var pair in jsonValue.Values)
        {
            writer.WritePropertyName(pair.Key);
            writer.WriteValue(pair.Value);
        }
        writer.WriteEndObject();
    }
}

注意[JsonConverter(typeof(JsonValueListConverter))] 的使用。这指定在序列化和反序列化JsonValueList 时使用自定义转换器。

接下来,如下定义您的Update_DB 类:

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<JsonValueList> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

现在您将能够成功地序列化和反序列化您的 JSON。示例fiddle

更新

如果你没有重复的键,你可以如下定义你的类:

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<Dictionary<string, string>> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

然后是以下内容:

var collection = new Update_DB
{
    data = new List<Dictionary<string, string>>
    {
        new Dictionary<string, string>
        {
            {"data1", "10551296"},
            {"data2", "TrainingIns"},
            {"data3", "Completed"},
        },
        new Dictionary<string, string>
        {
            {"connectorType", "webserver-to-appserver"},
            {"sourceUri", "data4"},
            {"destinationUri", "data5"},
        },
    },
};

string x = JsonConvert.SerializeObject(collection.data, Formatting.Indented);

Console.WriteLine(x);

产生输出:

[
  {
    "data1": "10551296",
    "data2": "TrainingIns",
    "data3": "Completed"
  },
  {
    "connectorType": "webserver-to-appserver",
    "sourceUri": "data4",
    "destinationUri": "data5"
  }
]

示例fiddle

【讨论】:

  • 我感觉很糟糕,因为我只是快速编辑了数据,没有工作数据,但通常不会有重复的键名。真的,我一直在寻找这个List&lt;Dictionary&lt;string, string&gt;&gt;。虽然当我做JsonConvert.SerializeObject(collection.data); 时,我只会得到[{},{},{},{},{},{},{}]。我是否必须做一些不同的事情来序列化它,因为它很复杂?对不起,如果这是一个非常简单的问题!
  • @JoeyGelpi - 我在您提供的 JSON 上测试了上面的代码,它可以工作。请参阅fiddle。如果您实际上有不同的 JSON 或不同的要求,那么如果没有 minimal reproducible example,我无法说出您的问题。
  • 与 JSON 格式相同,只是没有重复。你的小提琴看起来根本不使用 JsonValueList 类,所以我认为它和我的一样。由于您只是使用字符串,因此数据合同的转换可能会丢失某些内容。我会看看这个,但我今天出去了。如果没有,我将尝试将其剥离并举一个完整的例子。感谢您的帮助!
  • @JoeyGelpi - 一旦明确没有重复的键,我就切换到字典格式。无论如何,我更新了 original fiddle 以也序列化 collection.data 并且生成的字符串看起来不错。
  • 不同之处在于你显式地反序列化了字符串。我使用了一个 WebInvoke,当我执行 ResponseJsonType WriteToDatabase(Update_DB collection); 时它似乎会自动反序列化我尝试将集合作为字符串发送并像你一样反序列化它,但我遇到了更大的错误。我不知道我会继续努力。感谢您的帮助/
【解决方案3】:

另一种选择是使用 dynamic 关键字。您可以将这种类型的列表用于数据(如下所示)。

[DataContract]
public class Update_DB
{
    [DataMember(Name = "appname", IsRequired = true)]
    public string appname { get; set; }
    [DataMember]
    public string key { get; set; }

    [DataMember(Name = "data", IsRequired = true)]
    public List<dynamic> data { get; set; }

    [DataMember]
    public string updateId { get; set; }
    [DataMember]
    public string updateTS { get; set; }
    [DataMember]
    public string creationUser { get; set; }
}

从那里,您可以通过 JSON.Net 反序列化来使用该对象,并访问动态数据对象(假设您对该动态对象的形状有所了解)。下面的内容将根据原始帖子中的输入字符串起作用。

Update_DB dataObj = JsonConvert.DeserializeObject<Update_DB>(objectJSON);
string test = dataObj.data[1].connectorType; //evaluates to "webserver-to-appserver"

【讨论】:

    猜你喜欢
    • 2016-07-06
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    • 2020-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多