NewtonSoft JsonConverter-访问其他属性

Bob*_*ale 4 c# json.net

我需要将小数的输出json格式化为货币,并在要序列化的对象中指定文化,对象可以嵌套,因此无法在序列化器中预设选项。我当前的操作方式是使用额外的字符串属性来格式化输出。

[JsonIgnore]
public decimal Cost {get;set;}

[JsonIgnore]
public CultureInfo Culture {get;set;}

public string AsCurrency(decimal value) {
  return string.Format(this.Culture, "{0:c}", value);
}

[JsonProperty("FormattedCost")]
public string FormatedCost {
  get { return this.AsCurrency(this.Cost); }
}
Run Code Online (Sandbox Code Playgroud)

我有很多要处理的属性,我不介意反序列化,另一种语言使用JsonObject填充PDF,所以我想要字符串值。

理想情况下,我想要一个,JsonConverter所以我可以做

[JsonProperty("FormattedCost")]
[JsonConverter(typeof(MyCurrencyConverter))]
public decimal Cost {get;set;}
Run Code Online (Sandbox Code Playgroud)

我的问题是如何访问转换器中包含对象的Culture属性。

public class MyCurrencyConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
       var culture = // How do I get the Culture from the parent object?
       writer.WriteValue(string.format(culture, "{0:c}", (decimal)value);

    }

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

    public override bool CanConvert(Type objectType)
    {
        return typeof(decimal) == objectType;
    }
}
Run Code Online (Sandbox Code Playgroud)

根据请求的示例JSON。

Contract每种都有成本和文化的课程。

[{ FormattedCost : "£5000.00"}, { FormattedCost : "$8000.00"}, { FormattedCost : "€599.00"}]
Run Code Online (Sandbox Code Playgroud)

实际的对象要复杂得多,具有嵌套资产的多个字段将具有自己的图形。此外,并非所有的小数点都是货币。

我真的不需要为Contract本身编写自定义序列化程序,因为每次属性更改时我都必须对其进行修改。

理想的解决方案是能够使用转换器属性标记某些十进制属性,以便它可以处理它。

我想考虑的另一种方法是使用十进制的隐式转换为十进制属性创建一个自定义类,但是由于某些属性是根据先前结果计算的属性,因此变得更加复杂。

解决方法

对于我的用例,我有一个解决方法,但是它使用反射在序列化器中获取私有变量。

var binding = BindingFlags.NonPublic | BindingFlags.Instance;
var writer = serializer.GetType()
                       .GetMethod("GetInternalSerializer", binding)
                       ?.Invoke(serializer, null);
var parent = writer?.GetType()
                   .GetField("_serializeStack", binding)
                   ?.GetValue(writer) is List<object> stack 
                        && stack.Count > 1 ? stack[stack.Count - 2] as MyType: null;
Run Code Online (Sandbox Code Playgroud)

在我经过测试的用例中,这为我提供了父对象,但未使用公共API。

dbc*_*dbc 5

您要做的是在对对象的特定属性进行序列化时,对其进行拦截和修改,同时对所有其他属性使用默认序列化。这可以通过一个自定义ContractResolver方法来完成,该自定义方法将在ValueProvider应用特定属性时替换所讨论的属性。

首先,定义以下属性和合同解析器:

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public class JsonFormatAttribute : System.Attribute
{
    public JsonFormatAttribute(string formattingString)
    {
        this.FormattingString = formattingString;
    }

    /// <summary>
    /// The format string to pass to string.Format()
    /// </summary>
    public string FormattingString { get; set; }

    /// <summary>
    /// The name of the underlying property that returns the object's culture, or NULL if not applicable.
    /// </summary>
    public string CulturePropertyName { get; set; }
}

public class FormattedPropertyContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization)
            .AddFormatting();
    }
}

public static class JsonContractExtensions
{
    class FormattedValueProvider : IValueProvider
    {
        readonly IValueProvider baseProvider;
        readonly string formatString;
        readonly IValueProvider cultureValueProvider;

        public FormattedValueProvider(IValueProvider baseProvider, string formatString, IValueProvider cultureValueProvider)
        {
            this.baseProvider = baseProvider;
            this.formatString = formatString;
            this.cultureValueProvider = cultureValueProvider;
        }

        #region IValueProvider Members

        public object GetValue(object target)
        {
            var value = baseProvider.GetValue(target);
            var culture = cultureValueProvider == null ? null : (CultureInfo)cultureValueProvider.GetValue(target);
            return string.Format(culture ?? CultureInfo.InvariantCulture, formatString, value);
        }

        public void SetValue(object target, object value)
        {
            // This contract resolver should only be used for serialization, not deserialization, so throw an exception.
            throw new NotImplementedException();
        }

        #endregion
    }

    public static IList<JsonProperty> AddFormatting(this IList<JsonProperty> properties)
    {
        ILookup<string, JsonProperty> lookup = null;

        foreach (var jsonProperty in properties)
        {
            var attr = (JsonFormatAttribute)jsonProperty.AttributeProvider.GetAttributes(typeof(JsonFormatAttribute), false).SingleOrDefault();
            if (attr != null)
            {
                IValueProvider cultureValueProvider = null;
                if (attr.CulturePropertyName != null)
                {
                    if (lookup == null)
                        lookup = properties.ToLookup(p => p.UnderlyingName);
                    var cultureProperty = lookup[attr.CulturePropertyName].FirstOrDefault();
                    if (cultureProperty != null)
                        cultureValueProvider = cultureProperty.ValueProvider;
                }
                jsonProperty.ValueProvider = new FormattedValueProvider(jsonProperty.ValueProvider, attr.FormattingString, cultureValueProvider);
                jsonProperty.PropertyType = typeof(string);
            }
        }
        return properties;
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,定义您的对象,如下所示:

public class RootObject
{
    [JsonFormat("{0:c}", CulturePropertyName = nameof(Culture))]
    public decimal Cost { get; set; }

    [JsonIgnore]
    public CultureInfo Culture { get; set; }

    public string SomeValue { get; set; }

    public string SomeOtherValue { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

最后,序列化如下:

var settings = new JsonSerializerSettings
{
    ContractResolver = new FormattedPropertyContractResolver
    {
        NamingStrategy = new CamelCaseNamingStrategy(),
    },
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. 由于您没有序列化区域性名称,因此看不到任何反序列化Cost属性的方法。因此,我从该SetValue方法中抛出了一个例外。

    (而且,即使您要对区域性名称进行序列化,由于JSON对象是根据标准无序名称/值对集合,也无法保证区域性名称在反序列化JSON之前不会出现。这可能是与为什么Newtonsoft不提供对父堆栈的访问权限有关。在反序列化期间,不能保证已读取父层次结构中的必需属性-甚至不能保证已经构造了父级。)

  2. 如果您必须对合同应用几种不同的自定义规则,请考虑使用ConfigurableContractResolverfrom 如何添加元数据来描述JSON.Net中哪些属性是日期

  3. 您可能需要缓存合同解析器以获得最佳性能。

  4. 另一种方法是将转换器添加到父对象,该转换器JObject通过临时禁用自身,调整返回的值JObject,然后将其写出,来生成默认的序列化。有关此方法的示例,请参见JSON.Net在使用时抛出StackOverflowException[JsonConvert()]或是否可以在Json.net的一次操作中将嵌套属性序列化到类中?

  5. 在您写的注释中,在WriteJson内部,我无法弄清楚如何访问父对象及其属性。 可以使用一个IValueProvider返回Tuple包含父级和值的类或类似类的自定义方法来做到这一点,该类将与JsonConverter期望此类输入的特定对象一起使用。不确定是否建议这样做,因为它非常棘手。

工作样品净小提琴


归档时间:

查看次数:

1375 次

最近记录:

8 年,1 月 前