【问题标题】:ArgumentOutOfRange exception when deserializing to a DataSet with Json.Net使用 Json.Net 反序列化为 DataSet 时出现 ArgumentOutOfRange 异常
【发布时间】:2014-05-09 20:45:04
【问题描述】:

我在一个名为 strJSON 的变量中有以下 JSON 字符串。

{
   "results":[
      {
         "templateName":"HUD Section 8",
         "userID":"2",
         "mobileObjectId":"4582",
         "source":"M",
         "inspectionType":"A",
         "notes":"Window in bedroom needs repair.",
         "agencyID":"",
         "requestDate":"2014-05-09 00:00:00",
         "agencyName":"",
         "inspectionTimeBegun":"2014-05-09 14:00:17",
         "inspectionDate":"2014-05-09 14:30:00",
         "inspectionID":135,
         "inspectionTimeComplete":"2014-05-09 14:29:25",
         "summaryDecision":"F",
         "createdAt":"2014-05-09T18:29:35.050Z",
         "updatedAt":"2014-05-09T18:29:35.050Z",
         "objectId":"1FgtD6WT8Y",
         "ACL":{
            "*":{
               "read":true
            },
            "cryZoU5gXJ":{
               "write":true,
               "read":true
            }
         }
      }
   ]
}

当我调用以下代码行时...

ds = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DataSet)(strJSON)

我收到一条异常消息,指出“指定的参数超出了有效值的范围”

JSON 字符串是通过对 Parse.com 的以下 REST API 调用创建的。

strJSON = http.QuickGetStr(strURL)

我在其他地方成功地使用了它,尽管使用了更简单的 Parse 类,但我仔细检查了这个 JSON 字符串,没有发现任何错误。

关于可能导致此错误的任何想法?

【问题讨论】:

  • 感谢您的回复。 DataSet 确实有效,因为我让它在具有不同 Parse 类的其他部分中工作。我以为你在用星号做一些事情,所以我确保用 User 替换它,但我仍然收到错误。

标签: json vb.net json.net


【解决方案1】:

为了让 Json.Net 反序列化为 DataSet,JSON 必须采用特定格式,如 this answer 中所述。您的 JSON 很接近,但问题是 ACL 对象。 Json.Net 5.0 使用的 DataTableConverter 要求表中的所有列都是简单数据类型,否则将抛出 ArgumentOutOfRangeException (source)。 Json.Net 6.0 除了简单类型外,还支持嵌套数据表和数组,但是您的 ACL 数据仍然不符合允许将其正确反序列化为 DataSet 的所需格式。你有几个不同的选择来处理这个问题:

更改 JSON

如果您控制 JSON 的格式(即它不是来自第三方),您可以更改它,以便 Json.Net 6.0 能够将其反序列化为 DataSet。以下是它需要的样子:

{
    "results": [
        {
            "templateName": "HUD Section 8",
            "userID": "2",
            "mobileObjectId": "4582",
            "source": "M",
            "inspectionType": "A",
            "notes": "Window in bedroom needs repair.",
            "agencyID": "",
            "requestDate": "2014-05-09 00:00:00",
            "agencyName": "",
            "inspectionTimeBegun": "2014-05-09 14:00:17",
            "inspectionDate": "2014-05-09 14:30:00",
            "inspectionID": 135,
            "inspectionTimeComplete": "2014-05-09 14:29:25",
            "summaryDecision": "F",
            "createdAt": "2014-05-09T18:29:35.050Z",
            "updatedAt": "2014-05-09T18:29:35.050Z",
            "objectId": "1FgtD6WT8Y",
            "ACL": [
                {
                    "user": "*",
                    "read": true,
                    "write": false
                },
                {
                    "user": "cryZoU5gXJ",
                    "read": true,
                    "write": true
                }
            ]
        }
    ]
}

使用这种格式,results 表的ACL 列将包含一个嵌套的DataTable,其中包含单独的 ACL 行,每行包含三列,userreadwrite

反序列化为强类型类

您可以反序列化为一组强类型类,而不是反序列化为DataSet。这种方法的优点是一切都是易于使用的形式。缺点是您需要先了解 JSON 中的内容,然后才能创建类。

您可以使用像 json2csharp.com 这样的第三方工具来帮助从 JSON 样本生成类,正如另一个答案(现已删除)中所建议的那样,但请注意,这不是万无一失的(而且它不是做VB)。有时您需要手动干预和编辑类。例如,如果从您的问题中的 JSON 生成类,您会注意到它为每个 ACL 实例创建一个固定类。除非您的 ACL 集始终恰好有两个项目,否则这将不起作用,一个称为 Everyone,另一个称为 CryZoU5gXJ。我认为 ACL 集更有可能是可变的,因此对这些使用 Dictionary 是有意义的。以下是我建议的课程:

Class RootObject
    Public Property results As List(Of Result)
End Class

Class Result
    Public Property templateName As String
    Public Property userID As String
    Public Property mobileObjectId As String
    Public Property source As String
    Public Property inspectionType As String
    Public Property notes As String
    Public Property agencyID As String
    Public Property requestDate As String
    Public Property agencyName As String
    Public Property inspectionTimeBegun As String
    Public Property inspectionDate As String
    Public Property inspectionID As Integer
    Public Property inspectionTimeComplete As String
    Public Property summaryDecision As String
    Public Property createdAt As String
    Public Property updatedAt As String
    Public Property objectId As String
    Public Property ACL As Dictionary(Of String, ACL)
End Class

Class ACL
    Public Property read As Boolean
    Public Property write As Boolean
End Class

有了这个类结构,你可以像这样反序列化:

Dim root As RootObject = JsonConvert.DeserializeObject(Of RootObject)(strJSON)

对于 ACL,每个字典条目的键将是用户 ID(或 *,如您在示例中所使用的)。如果您实际上并不关心 ACL,您可以简单地从 Result 类中省略 ACL 属性。默认情况下,Json.Net 会跳过 JSON 中存在但类中不存在的属性。

使用 LINQ-to-JSON API 解析 JSON

使用 Json.Net 总是有不止一种方法可以给猫剥皮。 Json.Net 的LINQ-to-JSON API 在您解析的 JSON 高度可变和/或您不想创建用于接收数据的类时真的很出色。您可以将任何有效的 JSON 反序列化为 JToken 对象的层次结构,然后根据需要将它们分开。例如,如果您只需要从每个结果中选择几条信息,您可以这样做:

Dim token As JToken = JToken.Parse(json)

For Each result As JObject In token("results").Children(Of JObject)()
    Console.WriteLine("userID: " + result("userID").ToString())
    Console.WriteLine("templateName: " + result("templateName").ToString())
    Console.WriteLine("inspectionID: " + result("inspectionID").ToString())
    Console.WriteLine("inspectionType: " + result("inspectionType").ToString())
    Console.WriteLine("inspectionDate: " + result("inspectionDate").ToString())
    Console.WriteLine("summaryDecision: " + result("summaryDecision").ToString())
    Console.WriteLine("notes: " + result("notes").ToString())
Next

您可以使用相同的方法从 JSON 手动构建 DataSet。这是一个通用函数,它将 JSON 反序列化为 DataSet,但忽略任何复杂对象(例如 ACL)而不是抛出异常:

Function DeserializeToDataSet(json As String) As DataSet

    Dim root As JObject = JObject.Parse(json)
    Dim ds As DataSet = New DataSet()

    For Each prop As JProperty In root.Properties

        If prop.Value.Type = JTokenType.Array Then

            Dim dt As DataTable = ds.Tables.Add(prop.Name)
            For Each row As JObject In prop.Value.Children(Of JObject)()

                Dim dr As DataRow = dt.NewRow
                For Each col As JProperty In row.Properties

                    Dim colType As Type = GetColumnType(col.Value.Type)
                    If Not colType Is Nothing Then

                        Dim dc As DataColumn = dt.Columns(col.Name)
                        If dc Is Nothing Then
                            dc = dt.Columns.Add(col.Name, colType)
                        End If

                        dr(col.Name) = col.Value.ToObject(colType)
                    End If
                Next

                dt.Rows.Add(dr)
            Next
        End If
    Next

    Return ds

End Function

Function GetColumnType(tokenType As JTokenType) As Type

    If tokenType = JTokenType.String Then Return GetType(String)
    If tokenType = JTokenType.Integer Then Return GetType(Integer)
    If tokenType = JTokenType.Date Then Return GetType(DateTime)
    If tokenType = JTokenType.Boolean Then Return GetType(Boolean)
    If tokenType = JTokenType.Float Then Return GetType(Double)
    Return Nothing

End Function

当然,如果您需要 ACL,则需要自定义此方法以将该数据放入您的代码可以使用的表单中。我会把这部分留给你。

【讨论】:

  • 谢谢布赖恩。看起来你投入的时间比我多。我打算尝试从 JSON 中删除 ACL,但我会在今天早上尝试您的解决方案。谢谢!
  • 布赖恩,感谢您的帖子。该解决方案通过从结果集中消除 ACL 结构来发挥作用。感谢您的帮助!
【解决方案2】:

Json.Net 只有在符合特定标准的情况下才会直接解析为 DataSet。请参阅this answer 了解所需的布局。

但是,您可以反序列化为 XML 文档并使用 DataSet 对象的 ReadXml 方法为您加载它。有关如何执行此操作的详细信息,请参阅this question

(HT to Brian Rogers 获取数据集结构详细信息)

【讨论】:

    猜你喜欢
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-09
    相关资源
    最近更新 更多