C# NewtonSoft JsonConverter-访问其他属性

C# NewtonSoft JsonConverter-访问其他属性,c#,json.net,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.

我需要将十进制的输出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); }
}
我有很多属性要处理,我不担心反序列化,JsonObject被另一种语言用来填充PDF,所以我需要字符串值

理想情况下,我想要一个
JsonConverter
,这样我就可以

[JsonProperty("FormattedCost")]
[JsonConverter(typeof(MyCurrencyConverter))]
public decimal Cost {get;set;}
我遇到的问题是如何访问转换器中包含对象的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;
    }
}
按要求提供示例JSON

对于每个都有成本和区域性的
合同
类数组

[{ FormattedCost : "£5000.00"}, { FormattedCost : "$8000.00"}, { FormattedCost : "€599.00"}]
实际对象要复杂得多,多个字段都有嵌套的资产,这些资产都有自己的图形。此外,并非所有小数都是货币

我真的不想为契约本身编写自定义序列化程序,因为每次属性更改时,我都必须修改它

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

我考虑的另一种方法是为decimal属性创建一个自定义类,并从decimal进行隐式转换,但是由于某些属性是基于以前的结果计算出来的属性,因此这会变得更加复杂

解决方法

我的用例有一个变通方法,但它使用反射在序列化程序中获取一个私有变量

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;
var binding=BindingFlags.NonPublic | BindingFlags.Instance;
var writer=serializer.GetType()
.GetMethod(“GetInternalSerializer”,绑定)
?调用(序列化程序,null);
var parent=writer?.GetType()
.GetField(“\u序列化堆栈”,绑定)
?.GetValue(编写器)是列表堆栈
&&堆栈.计数>1?堆栈[stack.Count-2]为MyType:null;

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

您要做的是在对象的特定属性被序列化时拦截并修改其值,同时对所有其他属性使用默认序列化。这可以通过在应用特定属性时替换相关属性的

首先,定义以下属性和协定解析程序:

[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;
    }
}
最后,序列化如下:

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; }
}
var settings = new JsonSerializerSettings
{
    ContractResolver = new FormattedPropertyContractResolver
    {
        NamingStrategy = new CamelCaseNamingStrategy(),
    },
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
注:

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

    (而且,即使您序列化了区域性名称,由于JSON对象是一组无序的名称/值对,因此无法保证区域性名称出现在反序列化JSON的开销之前。这可能与Newtonsoft不提供对父堆栈的访问有关。在反序列化期间但是,不能保证父层次结构中所需的属性已被读取,甚至不能保证父层次结构已被构造。)

  • >P>如果您必须将几种不同的自定义规则应用到您的合同中,请考虑使用<代码> OutababLeNracTraceRever < /代码>从.

  • 您可能希望获得最佳性能

  • 另一种方法是向父对象添加一个转换器,该转换器通过临时禁用自身来生成对
    JObject
    的默认序列化,调整返回的
    JObject
    ,然后将其写出。有关此方法的示例,请参阅或

  • 在您编写的注释中,在WriteJson内部,我无法理解如何访问父对象及其属性。应该可以使用自定义的
    IValueProvider
    来执行此操作,该提供程序返回一个
    元组或类似的类,其中包含父对象和值,该类将与特定的
    JsonConverte一起使用r
    需要这样的输入。但我不确定我会推荐这样做,因为这非常棘手


  • 工作示例。

    您是否尝试过编写
    MyCurrencyConverter
    ?(PS您的第一个代码块未编译)@DavidG是的,但问题出在writeJson内部。我不知道如何访问父对象及其属性。告诉我们您到目前为止拥有的内容,然后我们可以提供帮助。因此可以有多个嵌套对象,每个对象都有自己的区域性?也许您也可以提供一些JSON示例?太棒了,谢谢,这解决了我的问题,我将其改编为m我接受了这个答案,因为它回答了我提出的问题。我现在必须找出一种方法来处理文化处于一个更高的层次,但我知道正确的方法。