在过滤掉某些属性的同时序列化JSON.NET JObject

时间:2018-12-07 12:50:42

标签: c# .net json.net

我有一个很大的任意JSON结构作为代码中的JObject引用。

我想序列化此结构,除非遇到JObject包含名为type且值为"encrypted"的属性,然后要删除相邻的data属性编写对象。

换句话说,如果我遇到这个问题:

{
  type: "encrypted",
  name: "some-name",
  data: "<base64-string>"
}

它将被序列化为:

{
  type: "encrypted",
  name: "some-name"
}

我无法更改结构,因此在更改之前克隆它效率太低,因此我尝试使用JsonConverter如下:

public class RemoveEncryptedDataSerializer : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(JObject);
    }

    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)
    {
        var o = (JObject)value;
        if (o.Value<string>("type") != "encrypted")
        {
            o.WriteTo(writer);
            return;
        }

        var copy = o.DeepClone();
        copy["data"]?.Parent.Remove();
        copy.WriteTo(writer);
    }
}

然而,似乎仅使用不是从CanConvert派生的类型来调用JToken函数,因此从未调用我的WriteJson函数。

还有另一种方法可以实现这一目标吗?


编辑:以下是一些可用于测试的代码:

[TestMethod]
public void ItShouldExcludeEncryptedData()
{
    var input = JObject.Parse(@"
    {
        a: {
            type: 'encrypted',
            name: 'some-name',
            data: 'some-data'
        }
    }");

    var expected = JObject.Parse(@"
    {
        a: {
            type: 'encrypted',
            name: 'some-name',
        }
    }");

    var output = input.ToString(Formatting.Indented, new RemoveEncryptedDataSerializer());

    Assert.AreEqual(
        expected.ToString(Formatting.Indented),
        output);
}

2 个答案:

答案 0 :(得分:2)

需要构建转换器来处理JToken,并且该转换器必须递归工作以确保编辑所有加密数据。

我能够使以下转换器正常工作:

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

    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 token = (JToken)value;
        if (token.Type == JTokenType.Object)
        {
            bool omitDataProperty = token.Value<string>("type") == "encrypted";

            writer.WriteStartObject();
            foreach (var prop in token.Children<JProperty>())
            {
                if (omitDataProperty && prop.Name == "data")
                    continue;

                writer.WritePropertyName(prop.Name);
                serializer.Serialize(writer, prop.Value);  // recurse
            }
            writer.WriteEndObject();
        }
        else if (token.Type == JTokenType.Array)
        {
            writer.WriteStartArray();
            foreach (var item in token.Children())
            {
                serializer.Serialize(writer, item);  // recurse
            }
            writer.WriteEndArray();
        }
        else // JValue
        {
            token.WriteTo(writer);
        }
    }
}

正在运行的演示:https://dotnetfiddle.net/0K61Bz


如果要直接通过流使用JsonWriter,则可以将转换器中的逻辑重构为递归扩展方法,并使用该方法。如果不使用串行器,则不需要转换器。

public static class JsonExtensions
{
    public static void RedactedWriteTo(this JToken token, JsonWriter writer)
    {
        if (token.Type == JTokenType.Object)
        {
            bool omitDataProperty = token.Value<string>("type") == "encrypted";

            writer.WriteStartObject();
            foreach (var prop in token.Children<JProperty>())
            {
                if (omitDataProperty && prop.Name == "data")
                    continue;

                writer.WritePropertyName(prop.Name);
                prop.Value.RedactedWriteTo(writer);  // recurse
            }
            writer.WriteEndObject();
        }
        else if (token.Type == JTokenType.Array)
        {
            writer.WriteStartArray();
            foreach (var item in token.Children())
            {
                item.RedactedWriteTo(writer);  // recurse
            }
            writer.WriteEndArray();
        }
        else // JValue
        {
            token.WriteTo(writer);
        }
    }
}

然后您可以像这样使用它,其中stream是您的输出流,input是您的JObject

using (StreamWriter sw = new StreamWriter(stream))  // or StringWriter if you prefer
using (JsonWriter writer = new JsonTextWriter(sw))
{
    writer.Formatting = Formatting.Indented;
    input.RedactedWriteTo(writer);
}

提琴:https://dotnetfiddle.net/l949HU

答案 1 :(得分:0)

假设您正在使用Newtonsoft JSON.Net库;

要有条件地序列化属性,请添加一个方法,该方法返回与属性名称相同的布尔值,然后在方法名称前加上ShouldSerialize。方法的结果确定属性是否被序列化。如果该方法返回true,则该属性将被序列化;如果返回false,则该属性将被跳过。

例如:

public class EncryptedData
{
    public string Type { get; set; }
    public string Name { get; set; }
    public string Data { get; set; }

    public bool ShouldSerializeData()
    {
        // don't serialize the Data property if the Type equals "encrypted"
        return (Type != "encrypted");
    }
}