【问题标题】:Serialize Dynamic Property Name for an Object using JSON.NET使用 JSON.NET 序列化对象的动态属性名称
【发布时间】:2023-03-15 18:18:01
【问题描述】:

我正在使用 JSON.NET 对我的对象进行序列化以连接到 REST API。我的对象中需要序列化为 JSON 的属性之一具有动态属性名称。 如果该属性的结构中包含的值为数值,则 JSON 属性为“type_id”,但如果该值为字符串值,则 JSON 属性名称为“type_code”。我尝试为此使用自定义 JsonConverter,但是当我尝试序列化时,我收到带有此消息的 JsonWriterException

“状态属性中的令牌属性名称将导致无效的 JSON 对象。路径''。”

下面是我的对象的一个​​子集,如下所示,我没有在我的对象中为此指定属性名称:

[JsonProperty("title",Required=Required.Always,Order=1)]
public string Title { get; set; }

[JsonProperty("date",Order=3)]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Date { get; set; }

[JsonProperty(Order=2)]
[JsonConverter(typeof(TypeIdentifierJsonConverter))]
public TypeIdentifier DocTypeIdentifier { get; set; }

在 TypeIdentifier 类中,我的 WriteJson() 方法中有以下内容:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    TypeIdentifier docTypeId;
    id= (TypeIdentifier) value;

    writer.WritePropertyName(id.ParameterName);
    writer.WriteValue(id.Value);           
}

但是,我假设它默认为对象属性的名称而不是我的自定义名称,从而导致 JSON 字符串中的单个值有两个属性名称。如何为此动态设置属性名称,因为 JsonPropertyAttribute 标记在未明确指定时似乎会提取对象的属性名称?

注意:永远不需要从此应用反序列化此对象。

编辑:这个对象用[JsonObject(MemberSerialization.OptIn)]属性标记

【问题讨论】:

  • 你有对象的[Serializable]属性吗?
  • @Robert 使用 JSON.NET 而不是内置的 .NET 序列化意味着那些被忽略。不过,我确实为该类设置了[JsonObject(MemberSerialization.OptIn)] 属性,是的。

标签: c# json serialization json.net


【解决方案1】:

JsonConverter 无法设置父对象中的属性名称。调用转换器的WriteJson方法时,属性名已经写入JSON;作者只期望这一点的价值。这就是您收到错误的原因。为了完成这项工作,必须为父对象制作自定义转换器。然后,该转换器将负责编写其子项的属性名称和值。

跟进

可以为父对象编写一个转换器,以便仍然尊重应用到它的 JSON 属性,同时仍能实现您想要的结果。我将概述下面的方法。

首先,进行一些设置。由于您没有说明您的班级被称为什么,因此我将在此示例中假设它被称为Document。我们只需要对它做一个实质性的改变,那就是从DocTypeIdentifier 属性中删除[JsonConverter] 属性。所以我们有:

[JsonObject(MemberSerialization.OptIn)]
class Document
{
    [JsonProperty("title", Required = Required.Always, Order = 1)]
    public string Title { get; set; }

    [JsonProperty("date", Order = 3)]
    [JsonConverter(typeof(IsoDateTimeConverter))]
    public DateTime Date { get; set; }

    [JsonProperty(Order = 2)]
    public TypeIdentifier DocTypeIdentifier { get; set; }

    public string OtherStuff { get; set; }
}

您也没有显示 TypeIdentifier 类的代码,所以我假设它看起来像这样,例如:

class TypeIdentifier
{
    public string Value { get; set; }
    public string ParameterName { get; set; }
}

有了这些,我们就可以制作转换器了。该方法相当简单:我们将Document 加载到JObject 中,利用它尊重应用属性的事实,然后返回并修复DocTypeIdentifier 的序列化,因为它需要特殊处理。一旦我们有了这个,我们将JObject写到JsonWriter。代码如下:

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Document doc = (Document)value;

        // Create a JObject from the document, respecting existing JSON attribs
        JObject jo = JObject.FromObject(value);

        // At this point the DocTypeIdentifier is not serialized correctly.
        // Fix it by replacing the property with the correct name and value.
        JProperty prop = jo.Children<JProperty>()
                           .Where(p => p.Name == "DocTypeIdentifier")
                           .First();

        prop.AddAfterSelf(new JProperty(doc.DocTypeIdentifier.ParameterName, 
                                        doc.DocTypeIdentifier.Value));
        prop.Remove();

        // Write out the JSON
        jo.WriteTo(writer);
    }

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

现在我们有了转换器,但问题是我们不能简单地用[JsonConverter] 属性装饰Document 类来使用它。如果我们这样做了,当我们将文档加载到JObject 中时,转换器会尝试使用自身,因此最终会出现递归循环。因此,我们需要创建一个转换器实例,并通过设置将其传递给序列化器。转换器的CanConvert 方法确保它在正确的类上使用。 JObject.FromObject 方法在内部使用不同的序列化程序实例,因此它看不到DocumentConverter,因此不会遇到麻烦。

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new DocumentConverter());

string json = JsonConvert.SerializeObject(doc, settings);

这是一个展示转换器的演示:

class Program
{
    static void Main(string[] args)
    {
        Document doc = new Document
        {
            Title = "How to write a JSON converter",
            Date = DateTime.Today,
            DocTypeIdentifier = new TypeIdentifier
            {
                ParameterName = "type_id",
                Value = "26"
            },
            OtherStuff = "this should not appear in the JSON"
        };

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new DocumentConverter());
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(doc, settings);
        Console.WriteLine(json);
    }
}

这是上面的输出:

{
  "title": "How to write a JSON converter",
  "type_id": "26",
  "date": "2014-03-28T00:00:00-05:00"
}

【讨论】:

  • 所以基本上我需要为整个对象编写一个转换器,并且使用属性进行自动序列化在这里不会奏效吗?由于我已经有一些内部对象的转换器,当我到达这些字段时,我可以从父转换器调用它们,这样我就不需要重新编写它们了吗?
  • 有一种方法可以编写转换器,以便仍然尊重属性的属性(这意味着应该自动调用其他转换器),但要注意的是,您不能放置 @987654344 @ 父对象上的属性,否则当转换器尝试使用自身时,它将导致递归循环。给我几分钟,我会试着做一个例子来演示。
  • 非常感谢。一个例子会很有帮助,我一直在努力研究一种解决方法,扩展 DefaultContractResolver 似乎并不真正适合我的需求,我也不想在一开始就使用那么多反射地点。
  • 没问题!我希望我能再次投票给你,非常感谢你的详细解释。它终于解决了我的问题,没有笨拙的解决方法!
猜你喜欢
  • 1970-01-01
  • 2015-04-20
  • 1970-01-01
  • 2013-04-24
  • 2021-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多