String Truncation during Json Deserialization and ADO Persistance

时间:2019-01-09 22:37:35

标签: c# json json.net ado.net deserialization

I am writing a console app that will hit a REST api, deserialize the JSON to a C# model and then persist that model using ADO into a db table.

First issue is that during the first run of the app we found that one of the JSON properties exceeded the column definition of nvarchar(300). We increased that column to nvarchar(4000), but I have no idea if any of the other dozen string properties might exceed the default of 300 I gave them.

FYI, the SQL error I got was:

String or binary data would be truncated.

The data for table-valued parameter "@Items" doesn't conform to the table type of the parameter. SQL Server error is: 8152, state: 10

The statement has been terminated.

...which makes sense if I was passing in a string with length 500 to an nvarchar(300)

So my desire: during deserialization or model creation in C# I would like to truncate the string properties/fields and give them a max length before I hit my persistence code so that I can ensure with 100% confidence that my fields will never exceed the nvarchar lengths and will never trigger the 'truncation error'.

I tried using System.ComponentModel.DataAnnotations and [MaxLength(4000)], but that seemed only for MVC and input validation during form POSTing.

I thought about making backing fields with custom setters, but that means having twice the lines of code in each of my entities. I have 9 entities and each probably has 2 dozen strings that I want to configure/truncate.

So question: is there any fancy way to truncate strings using some sort of NewtonSoft data annotation or a C# data annotation? Also, is there a magic way to avoid having a bazillion back fields? Or should I just make a custom string class and inherit from String that has a max length property?

1 个答案:

答案 0 :(得分:2)

Json.Net没有内置的字符串截断功能,但是您可以结合使用自定义ContractResolver和自定义ValueProvider来执行所需的操作。 ContractResolver将在所有类中查找字符串属性,并将ValueProvider应用于它们,而ValueProvider将在反序列化过程中进行实际的截断。您可以使解析器使用默认的最大长度300(或其他最大值),但也可以查找可能已应用于字符串属性的任何[MaxLength]属性(来自System.ComponentModel.DataAnnotations),并使用该长度来代替覆盖。这样就可以处理长度为4000的案件。

这是您需要的代码:

public class StringTruncatingPropertyResolver : DefaultContractResolver
{
    public int DefaultMaxLength { get; private set; }

    public StringTruncatingPropertyResolver(int defaultMaxLength)
    {
        DefaultMaxLength = defaultMaxLength;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // Apply a StringTruncatingValueProvider to all string properties
        foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
        {
            var attr = prop.AttributeProvider
                           .GetAttributes(true)
                           .OfType<MaxLengthAttribute>()
                           .FirstOrDefault();
            int maxLength = attr != null ? attr.Length : DefaultMaxLength;
            prop.ValueProvider = new StringTruncatingValueProvider(prop.ValueProvider, maxLength);
        }

        return props;
    }

    class StringTruncatingValueProvider : IValueProvider
    {
        private IValueProvider InnerValueProvider { get; set; }
        private int MaxLength { get; set; }

        public StringTruncatingValueProvider(IValueProvider innerValueProvider, int maxLength)
        {
            InnerValueProvider = innerValueProvider;
            MaxLength = maxLength;
        }

        // GetValue is called by Json.Net during serialization.
        // The target parameter has the object from which to read the string;
        // the return value is a string that gets written to the JSON.
        public object GetValue(object target)
        {
            return InnerValueProvider.GetValue(target);
        }

        // SetValue gets called by Json.Net during deserialization.
        // The value parameter has the string value read from the JSON;
        // target is the object on which to set the (possibly truncated) value.
        public void SetValue(object target, object value)
        {
            string s = (string)value;
            if (s != null && s.Length > MaxLength)
            {
                s = s.Substring(0, MaxLength);
            }
            InnerValueProvider.SetValue(target, s);
        }
    }
}

要使用解析器,请将其添加到JsonSerializerSettings的实例中,然后将设置传递给JsonConvert.DeserializeObject,如下所示:

var settings = new JsonSerializerSettings
{
    ContractResolver = new StringTruncatingPropertyResolver(300)
};
var foo = JsonConvert.DeserializeObject<Foo>(json, settings);

这是一个有效的演示:https://dotnetfiddle.net/YOGsP5