NewtonsoftJson 中的自定义 JSONConverter 用于序列化

Custom JSONConverter in NewtonsoftJson for Serialization

我想在 newtonsoftjson 库中编写自定义 JSON 转换器以进行序列化:

需求片段:

{
   "Roles":[
      {
         "Role":[
            {
               "LEAssociateTypeId":"101",
               "LEAssociateTypeId_Value":"Client/Counterparty",
               "LastUpdatedDate":"2021-11-30T08:35:01",
               "LegalEntityRoleStatusId":"3",
               "LegalEntityRoleStatusId_Value":"Active"
            },
            {
               "LEAssociateTypeId":"5501",
               "LEAssociateTypeId_Value":"Principal",
               "LastUpdatedDate":"2021-11-29T08:50:34",
               "LegalEntityRoleStatusId":"3",
               "LegalEntityRoleStatusId_Value":"Active"
            }
         ]
      }
   ]
}

收件人:

{
   "Roles":[
      {
         "Role":{
            "LEAssociateTypeId":"101",
            "LEAssociateTypeId_Value":"Client/Counterparty",
            "LastUpdatedDate":"2021-11-30T08:35:01",
            "LegalEntityRoleStatusId":"3",
            "LegalEntityRoleStatusId_Value":"Active"
         }
      },
      {
         "Role":{
            "LEAssociateTypeId":"5501",
            "LEAssociateTypeId_Value":"Principal",
            "LastUpdatedDate":"2021-11-29T08:50:34",
            "LegalEntityRoleStatusId":"3",
            "LegalEntityRoleStatusId_Value":"Active"
         }
      }
   ]
}

由于角色是一个动态对象,无法为其定义相应的class。

我还查看了在线文档 Role is converted to an array as per https://www.newtonsoft.com/json/help/html/convertingjsonandxml.html

编写的源代码:

public class customconverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken t = JToken.FromObject(value);
        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            JObject o = (JObject)t;
            foreach(var a in _validcollectionList)
            {
                if (o[a] != null && o[a].Count() > 0)
                {
                    var test = o[a][0];
                    var test1 = test["Role"];
                    var test2 = o[a] as JArray;
                    if (test1 != null && test1.Count() > 1)
                    {
                        foreach (var a1 in test1)
                        {
                            JObject obj = new JObject {
                                { "Role", a1 }
                            };
                            test2.Add(obj);
                        }
                    }
                    test.Remove();
                }
            }
            o.WriteTo(writer);
        }
    }
}

[11/12/2021] 更新:准确完整 json 就像:

{
  "Message": {
    "MessageInfo": {
      "Guid": "a2152d96-c202-4c08-a4a7-2331a648b586",
      "SourceId": "101",
    },
   "Roles":[
      {
         "Role":[
            {
               "LEAssociateTypeId":"101",
               "LEAssociateTypeId_Value":"Client/Counterparty",
               "LastUpdatedDate":"2021-11-30T08:35:01",
               "LegalEntityRoleStatusId":"3",
               "LegalEntityRoleStatusId_Value":"Active"
            },
            {
               "LEAssociateTypeId":"5501",
               "LEAssociateTypeId_Value":"Principal",
               "LastUpdatedDate":"2021-11-29T08:50:34",
               "LegalEntityRoleStatusId":"3",
               "LegalEntityRoleStatusId_Value":"Active"
            }
         ]
      }
   ]
}
}

实体或属性的数量也是动态的,因此以上内容可能会有所不同。

已检查解决方案,下面的代码行没有角色详细信息=

var semiParsedJson = JObject.Parse(json);

只有 messageinfo 存在,所以它没有解析完整 json。

我们也不能通过自定义 json 转换器来做,因为最初的目的是通过下面的代码行将 xml 转换为 json :

XmlDocument doc = new XmlDocument();
            doc.Load("XMLFile1.xml");
  string jsonText = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.Indented);

但 newtonsoftjson 库以某种方式将同一级别的相同名称的节点分组到一个数组中,所以这就是出现此查询的原因。

请多多指教。

[12/12/2021]:示例 XML 片段:

<Message>
  <MessageInfo>
    <Guid>be190914-4b18-4454-96ec-67887dd4d7a7</Guid>
    <SourceId>101</SourceId>
  </MessageInfo>
<LegalEntities>
 <LegalEntity>
 <Roles>
        <Role>
          <LEAssociateTypeId>101</LEAssociateTypeId>
          <LEAssociateTypeId_Value>Client/Counterparty</LEAssociateTypeId_Value>
          <LastUpdatedDate>2021-08-07T23:05:17</LastUpdatedDate>
          <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
          <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
        </Role>
        <Role>
          <LEAssociateTypeId>6000</LEAssociateTypeId>
          <LEAssociateTypeId_Value>Account Owner</LEAssociateTypeId_Value>
          <LastUpdatedDate>2021-08-07T21:20:07</LastUpdatedDate>
          <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
          <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
        </Role>
        <Role>
          <LEAssociateTypeId>5003</LEAssociateTypeId>
          <LEAssociateTypeId_Value>Investment Manager</LEAssociateTypeId_Value>
          <LastUpdatedDate>2021-08-16T06:12:59</LastUpdatedDate>
          <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
          <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
        </Role>
      </Roles>
 </LegalEntity>
 </LegalEntities>
</Message>

如果我的理解是正确的,那么你想做以下事情:

  • Roles 应该有两个 children 而不是一个
  • Role 不应该是一个集合,而是 属性

我认为进行此转换的最简单方法是通过 Linq to Json

static void Main(string[] args)
{
    var json = "...";
    var semiParsedJson = JObject.Parse(json);
    Console.WriteLine(TranformJson(semiParsedJson));
}

static string TranformJson(JObject semiParsed)
{
    if (!semiParsed.TryGetValue("Roles", out var roles))
        return null;
    if (roles is not JArray rolesArray)
        return null;

    var firstRole = (JObject)rolesArray.First();

    if (!firstRole.TryGetValue("Role", out var role))
        return null;
    if (role is not JArray roleArray)
        return null;

    var newStructure = new
    {
        Roles = roleArray.Children()
            .Select(role => new { Role = role })
    };

    return JsonConvert.SerializeObject(newStructure);
}
  1. 我们将 json 解析为 JObject 以帮助我们检索特定属性/集合
  2. 我们尝试检索 Roles 属性 如果它不存在那么我们 return null
  3. 如果它存在,那么我们检查它是否是一个集合。我在这里使用了 C# 9 的新 is not 特性
  4. 如果它不是集合那么我们 return 和 null
  5. 如果它是一个集合,那么我们从中检索第一个元素
  6. 我们执行相同的步骤 (2-4) 来检索 Role 集合
  7. 我们创建一个新的匿名 object 以所需方式表示数据
  8. 我们也对最内部的 object 进行了一些转换
  9. 最后我们将新构建的object序列化为字符串得到想要的输出
  10. 尽情享受吧! :)

我留下另一个 post 因为 OP 的问题与原始版本相比发生了很大变化。

只是为了确保我们的理解是一样的:

  • 您想使用 JsonConvert.SerializeXNode 将 xml 转换为 json
  • 默认情况下此方法不能优雅地处理数组
  • 您还想更改 Role 节点的表示,使其周围有一个包装器对象

让我们一个一个解决问题

XML 节点到 Json 数组

为了将 LegalEntitiesRoles 视为数组,您需要向这些 xml 节点添加自定义属性:json:Array = "true".

这个 json 命名空间必须像这样在 xml 中定义:xmlns:json="http://james.newtonking.com/projects/json"

因此,您需要将 xml 修改为此(手动或通过 System.Xml / System.Xml.Linq):

<Message xmlns:json="http://james.newtonking.com/projects/json">
   <MessageInfo>
      <Guid>be190914-4b18-4454-96ec-67887dd4d7a7</Guid>
      <SourceId>101</SourceId>
   </MessageInfo>
   <LegalEntities json:Array="true">
      <LegalEntity>
         <Roles json:Array="true">
            <Role>
               <LEAssociateTypeId>101</LEAssociateTypeId>
               <LEAssociateTypeId_Value>Client/Counterparty</LEAssociateTypeId_Value>
               <LastUpdatedDate>2021-08-07T23:05:17</LastUpdatedDate>
               <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
               <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
            </Role>
            <Role>
               <LEAssociateTypeId>6000</LEAssociateTypeId>
               <LEAssociateTypeId_Value>Account Owner</LEAssociateTypeId_Value>
               <LastUpdatedDate>2021-08-07T21:20:07</LastUpdatedDate>
               <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
               <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
            </Role>
            <Role>
               <LEAssociateTypeId>5003</LEAssociateTypeId>
               <LEAssociateTypeId_Value>Investment Manager</LEAssociateTypeId_Value>
               <LastUpdatedDate>2021-08-16T06:12:59</LastUpdatedDate>
               <LegalEntityRoleStatusId>3</LegalEntityRoleStatusId>
               <LegalEntityRoleStatusId_Value>Active</LegalEntityRoleStatusId_Value>
            </Role>
         </Roles>
      </LegalEntity>
   </LegalEntities>
</Message>

现在,如果您将其传递给 SerializeXNode,那么您将得到以下内容 json:

{
   "Message":{
      "MessageInfo":{
         "Guid":"be190914-4b18-4454-96ec-67887dd4d7a7",
         "SourceId":"101"
      },
      "LegalEntities":[
         {
            "LegalEntity":{
               "Roles":[
                  {
                     "Role":[
                        {
                           "LEAssociateTypeId":"101",
                           "LEAssociateTypeId_Value":"Client/Counterparty",
                           "LastUpdatedDate":"2021-08-07T23:05:17",
                           "LegalEntityRoleStatusId":"3",
                           "LegalEntityRoleStatusId_Value":"Active"
                        },
                        {
                           "LEAssociateTypeId":"6000",
                           "LEAssociateTypeId_Value":"Account Owner",
                           "LastUpdatedDate":"2021-08-07T21:20:07",
                           "LegalEntityRoleStatusId":"3",
                           "LegalEntityRoleStatusId_Value":"Active"
                        },
                        {
                           "LEAssociateTypeId":"5003",
                           "LEAssociateTypeId_Value":"Investment Manager",
                           "LastUpdatedDate":"2021-08-16T06:12:59",
                           "LegalEntityRoleStatusId":"3",
                           "LegalEntityRoleStatusId_Value":"Active"
                        }
                     ]
                  }
               ]
            }
         }
      ]
   }
}

变换Role个节点

为了能够执行一些转换,您需要通过 SelectToken 方法检索适当的节点

var doc = XDocument.Parse(xml);
var json = JsonConvert.SerializeXNode(doc);

var root = JObject.Parse(json);
var roles = root.SelectToken("$.Message.LegalEntities[0].LegalEntity.Roles") as JArray;
if (roles == null) return;

var role = roles.First().SelectToken("$.Role") as JArray;
if (roles == null) return;
  • 第一个 SelectToken 检索到 Roles 集合
  • 第二个 SelectToken 检索了 Role 集合

现在,让我们进行转换:

var roleNodes = new List<JObject>();
foreach (var roleNode in role)
{
    roleNodes.Add(new JObject(new JProperty("Role", roleNode)));
}

这里我们遍历 Role 集合,我们正在围绕它创建一个包装器对象,它有一个名为 Role 的 属性。

最后我们必须用新创建的 JObject 替换 Roles 集合。这可以通过以下简单命令实现:

roles.ReplaceAll(roleNodes);

为了完整起见,这里是完整的代码:

var xml = File.ReadAllText("sample.xml");

var doc = XDocument.Parse(xml);
var json = JsonConvert.SerializeXNode(doc);

var root = JObject.Parse(json);
var roles = root.SelectToken("$.Message.LegalEntities[0].LegalEntity.Roles") as JArray;
if (roles == null) return;

var role = roles.First().SelectToken("$.Role") as JArray;
if (roles == null) return;

var roleNodes = new List<JObject>();
foreach (var roleNode in role)
{
    roleNodes.Add(new JObject(new JProperty("Role", roleNode)));
}

roles.ReplaceAll(roleNodes);
Console.WriteLine(root);

和发出的输出:

{
  "Message": {
    "MessageInfo": {
      "Guid": "be190914-4b18-4454-96ec-67887dd4d7a7",
      "SourceId": "101"
    },
    "LegalEntities": [
      {
        "LegalEntity": {
          "Roles": [
            {
              "Role": {
                "LEAssociateTypeId": "101",
                "LEAssociateTypeId_Value": "Client/Counterparty",
                "LastUpdatedDate": "2021-08-07T23:05:17",
                "LegalEntityRoleStatusId": "3",
                "LegalEntityRoleStatusId_Value": "Active"
              }
            },
            {
              "Role": {
                "LEAssociateTypeId": "6000",
                "LEAssociateTypeId_Value": "Account Owner",
                "LastUpdatedDate": "2021-08-07T21:20:07",
                "LegalEntityRoleStatusId": "3",
                "LegalEntityRoleStatusId_Value": "Active"
              }
            },
            {
              "Role": {
                "LEAssociateTypeId": "5003",
                "LEAssociateTypeId_Value": "Investment Manager",
                "LastUpdatedDate": "2021-08-16T06:12:59",
                "LegalEntityRoleStatusId": "3",
                "LegalEntityRoleStatusId_Value": "Active"
              }
            }
          ]
        }
      }
    ]
  }
}