使用Json.Net动态删除JObject字段

时间:2016-10-18 19:03:29

标签: c# json json.net

我有一些json输入我无法预测的形状,我必须进行一些转换(称之为某些内容),以便不记录某些字段,例如,如果我有这个json

{
    "id": 5,
    "name": "Peter",
    "password": "some pwd"
}

然后在转换后它看起来像这样:

{
    "id": 5,
    "name": "Peter"
}  

样本很简单,实际情况不是那么开心/容易我会有一些正则表达式,如果输入json 上的任何字段匹配,那么就不会在结果上,如果我有一些嵌套对象,我将不得不递归。我一直在看 Linq到Json 的一些东西,但我发现没有什么能满足我的需求。

有没有办法做到这一点?

注意的: 这是日志记录库的一部分,如果需要或更容易,我可以使用json string。问题是,在我的日志记录管道中的某个时刻,我得到对象(或根据需要的字符串)然后我需要从敏感数据中删除它,例如密码,还有任何其他客户端 - 指定数据。

2 个答案:

答案 0 :(得分:4)

@dbc打败了我,但这是我的版本基本相同的想法。

您可以将JSON解析为JToken,然后使用递归帮助方法将属性名称与正则表达式匹配。只要匹配,您可以从其父对象中删除该属性。删除所有敏感信息后,只需使用JToken.ToString()获取编辑的JSON。

这是辅助方法的样子:

public static string RemoveSensitiveProperties(string json, IEnumerable<Regex> regexes)
{
    JToken token = JToken.Parse(json);
    RemoveSensitiveProperties(token, regexes);
    return token.ToString();
}

public static void RemoveSensitiveProperties(JToken token, IEnumerable<Regex> regexes)
{
    if (token.Type == JTokenType.Object)
    {
        foreach (JProperty prop in token.Children<JProperty>().ToList())
        {
            bool removed = false;
            foreach (Regex regex in regexes)
            {
                if (regex.IsMatch(prop.Name))
                {
                    prop.Remove();
                    removed = true;
                    break;
                }
            }
            if (!removed)
            {
                RemoveSensitiveProperties(prop.Value, regexes);
            }
        }
    }
    else if (token.Type == JTokenType.Array)
    {
        foreach (JToken child in token.Children())
        {
            RemoveSensitiveProperties(child, regexes);
        }
    }
}

以下是其使用的简短演示:

public static void Test()
{
    string json = @"
    {
      ""users"": [
        {
          ""id"": 5,
          ""name"": ""Peter Gibbons"",
          ""company"": ""Initech"",
          ""login"": ""pgibbons"",
          ""password"": ""Sup3rS3cr3tP@ssw0rd!"",
          ""financialDetails"": {
            ""creditCards"": [
              {
                ""vendor"": ""Viza"",
                ""cardNumber"": ""1000200030004000"",
                ""expDate"": ""2017-10-18"",
                ""securityCode"": 123,
                ""lastUse"": ""2016-10-15""
              },
              {
                ""vendor"": ""MasterCharge"",
                ""cardNumber"": ""1001200230034004"",
                ""expDate"": ""2018-05-21"",
                ""securityCode"": 789,
                ""lastUse"": ""2016-10-02""
              }
            ],
            ""bankAccounts"": [
              {
                ""accountType"": ""checking"",
                ""accountNumber"": ""12345678901"",
                ""financialInsitution"": ""1st Bank of USA"",
                ""routingNumber"": ""012345670""
              }
            ]
          },
          ""securityAnswers"":
          [
              ""Constantinople"",
              ""Goldfinkle"",
              ""Poppykosh"",
          ],
          ""interests"": ""Computer security, numbers and passwords""
        }
      ]
    }";

    Regex[] regexes = new Regex[]
    {
        new Regex("^.*password.*$", RegexOptions.IgnoreCase),
        new Regex("^.*number$", RegexOptions.IgnoreCase),
        new Regex("^expDate$", RegexOptions.IgnoreCase),
        new Regex("^security.*$", RegexOptions.IgnoreCase),
    };

    string redactedJson = RemoveSensitiveProperties(json, regexes);
    Console.WriteLine(redactedJson);
}

以下是结果输出:

{
  "users": [
    {
      "id": 5,
      "name": "Peter Gibbons",
      "company": "Initech",
      "login": "pgibbons",
      "financialDetails": {
        "creditCards": [
          {
            "vendor": "Viza",
            "lastUse": "2016-10-15"
          },
          {
            "vendor": "MasterCharge",
            "lastUse": "2016-10-02"
          }
        ],
        "bankAccounts": [
          {
            "accountType": "checking",
            "financialInsitution": "1st Bank of USA"
          }
        ]
      },
      "interests": "Computer security, numbers and passwords"
    }
  ]
}

小提琴:https://dotnetfiddle.net/KcSuDt

答案 1 :(得分:2)

您可以将JSON解析为JContainer(对象或数组),然后使用DescendantsAndSelf()搜索JSON层次结构,查找名称与某些Regex匹配的属性,或者与Regex匹配的字符串值,并使用JToken.Remove()删除这些项。

例如,给定以下JSON:

{
  "Items": [
    {
      "id": 5,
      "name": "Peter",
      "password": "some pwd"
    },
    {
      "id": 5,
      "name": "Peter",
      "password": "some pwd"
    }
  ],
  "RootPasswrd2": "some pwd",
  "SecretData": "This data is secret",
  "StringArray": [
    "I am public",
    "This is also secret"
  ]
}

您可以删除名称中包含"pass.*w.*r.*d"的所有属性,如下所示:

var root = (JContainer)JToken.Parse(jsonString);

var nameRegex = new Regex(".*pass.*w.*r.*d.*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
var query = root.DescendantsAndSelf()
    .OfType<JProperty>()
    .Where(p => nameRegex.IsMatch(p.Name));
query.RemoveFromLowestPossibleParent();

结果是:

{
  "Items": [
    {
      "id": 5,
      "name": "Peter"
    },
    {
      "id": 5,
      "name": "Peter"
    }
  ],
  "SecretData": "This data is secret",
  "StringArray": [
    "I am public",
    "This is also secret"
  ]
}

您可以通过执行以下操作删除包含子字符串secret的所有字符串值:

var valueRegex = new Regex(".*secret.*", RegexOptions.IgnoreCase);
var query2 = root.DescendantsAndSelf()
    .OfType<JValue>()
    .Where(v => v.Type == JTokenType.String && valueRegex.IsMatch((string)v));
query2.RemoveFromLowestPossibleParent();

var finalJsonString = root.ToString();

在第一次转换后应用时会导致:

{
  "Items": [
    {
      "id": 5,
      "name": "Peter"
    },
    {
      "id": 5,
      "name": "Peter"
    }
  ],
  "StringArray": [
    "I am public"
  ]
}

为方便起见,我使用以下扩展方法:

public static class JsonExtensions
{
    public static IEnumerable<TJToken> RemoveFromLowestPossibleParent<TJToken>(this IEnumerable<TJToken> nodes) where TJToken : JToken
    {
        if (nodes == null)
            return null;
        foreach (var node in nodes.ToList())
            node.RemoveFromLowestPossibleParent();
        return nodes;
    }

    public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
    {
        if (node == null)
            return null;
        var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
        if (contained != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (node.Parent is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
}