使用JSON.NET序列化对象的动态属性名

时间:2022-02-04 16:55:18

I'm using JSON.NET for serialization of my objects for connecting to a REST API. One of the properties in my object that needs to be serialized to JSON has a dynamic property name. If the value contained in the struct for this property is a numeric value, then the JSON property is "type_id", however if this value is a string value, then the JSON property name is "type_code". I attempted to use a custom JsonConverter for this, but I get a JsonWriterException with this message when I attempt to serialize:

我使用JSON。NET用于序列化我的对象以连接到REST API。我的对象中需要序列化为JSON的一个属性有一个动态属性名。如果这个属性的结构中包含的值是一个数值,那么JSON属性是“type_id”,但是如果这个值是一个字符串值,那么JSON属性名称是“type_code”。我尝试使用一个自定义JsonConverter来完成这个任务,但是当我尝试序列化时,我得到了一个JsonWriterException:

"Token PropertyName in state Property would result in an invalid JSON object. Path ''."

“state属性中的Token PropertyName将导致无效的JSON对象。”路径”。”

Below is a subset of my object, as seen below I didn't specify a property name in my object for that as such:

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

[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; }

In the TypeIdentifier class I have the following in my WriteJson() method:

在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);           
}

However, I am assuming it's defaulting to the name of the object's property instead of my custom one, causing two property names for a single value within the JSON string. How can the property name be set dynamically for this, since the JsonPropertyAttribute tag appears to pull the object's property name when not specified explicitly?

但是,我假设它默认为对象的属性名,而不是我的自定义属性名,这导致JSON字符串中单个值的两个属性名。如何动态地设置属性名,因为JsonPropertyAttribute标记在没有显式指定的情况下似乎会拉出对象的属性名?

NOTE: This object will never need to be deserialized from this app.

注意:这个对象永远不需要从这个应用程序中反序列化。

EDIT: This object is tagged with the [JsonObject(MemberSerialization.OptIn)] attribute

编辑:该对象被标记为[JsonObject(memberserializ.optin)]属性。

1 个解决方案

#1


22  

A JsonConverter cannot set the name of a property in a parent object. When the converter's WriteJson method is called, the property name has already been written to the JSON; the writer is expecting only a value that point. That is why you are getting an error. In order to make this work, the custom converter would have to be made for the parent object. That converter would then be responsible for writing the property names and values of its children.

JsonConverter不能在父对象中设置属性的名称。当调用转换器的WriteJson方法时,属性名称已经写入JSON;作者期望的只是这一点的价值。这就是为什么你会得到一个错误。为了使其工作,必须为父对象创建自定义转换器。然后,该转换器将负责编写其子元素的属性名和值。

Follow-up

后续

It is possible to write a converter for the parent object such that the JSON attributes applied to it are still respected, while still achieving the result you want. I'll outline the approach below.

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

First, a little bit of setup. Since you did not say what your class was called, I'll assume for this example that it is called Document. We only need to make one substantive change to it, and that is to remove the [JsonConverter] attribute from the DocTypeIdentifier property. So we have:

首先,设置一下。由于您没有说您的类被调用了什么,因此我将在本例中假设它被称为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; }
}

You also did not show the code for the TypeIdentifier class, so I'll just assume it looks like this, for sake of example:

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

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

With that out of the way, we can make the converter. The approach is fairly straightforward: we load the Document into a JObject, taking advantage of the fact that it respects the attributes applied, then go back and fix the serialization of the DocTypeIdentifier since it needs special handling. Once we have that, we write out the JObject to the JsonWriter. Here is the code:

有了这个,我们就可以做转换器了。这种方法非常简单:我们将文档加载到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();
    }
}

Now we have the converter, but the catch is we cannot simply decorate the Document class with a [JsonConverter] attribute in order to use it. If we did, we would end up with a recursive loop as the converter tried to use itself when we loaded the document into the JObject. So instead, we need to create an instance of the converter and pass it to the serializer via settings. The converter's CanConvert method ensures it gets used on the correct class. The JObject.FromObject method uses a different serializer instance internally, so it does not see the DocumentConverter and thus does not get into trouble.

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

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

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

Here is a demo showing the converter in action:

这是一个演示转换器在行动:

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);
    }
}

Here is the output from the above:

这是上面的输出:

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

#1


22  

A JsonConverter cannot set the name of a property in a parent object. When the converter's WriteJson method is called, the property name has already been written to the JSON; the writer is expecting only a value that point. That is why you are getting an error. In order to make this work, the custom converter would have to be made for the parent object. That converter would then be responsible for writing the property names and values of its children.

JsonConverter不能在父对象中设置属性的名称。当调用转换器的WriteJson方法时,属性名称已经写入JSON;作者期望的只是这一点的价值。这就是为什么你会得到一个错误。为了使其工作,必须为父对象创建自定义转换器。然后,该转换器将负责编写其子元素的属性名和值。

Follow-up

后续

It is possible to write a converter for the parent object such that the JSON attributes applied to it are still respected, while still achieving the result you want. I'll outline the approach below.

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

First, a little bit of setup. Since you did not say what your class was called, I'll assume for this example that it is called Document. We only need to make one substantive change to it, and that is to remove the [JsonConverter] attribute from the DocTypeIdentifier property. So we have:

首先,设置一下。由于您没有说您的类被调用了什么,因此我将在本例中假设它被称为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; }
}

You also did not show the code for the TypeIdentifier class, so I'll just assume it looks like this, for sake of example:

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

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

With that out of the way, we can make the converter. The approach is fairly straightforward: we load the Document into a JObject, taking advantage of the fact that it respects the attributes applied, then go back and fix the serialization of the DocTypeIdentifier since it needs special handling. Once we have that, we write out the JObject to the JsonWriter. Here is the code:

有了这个,我们就可以做转换器了。这种方法非常简单:我们将文档加载到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();
    }
}

Now we have the converter, but the catch is we cannot simply decorate the Document class with a [JsonConverter] attribute in order to use it. If we did, we would end up with a recursive loop as the converter tried to use itself when we loaded the document into the JObject. So instead, we need to create an instance of the converter and pass it to the serializer via settings. The converter's CanConvert method ensures it gets used on the correct class. The JObject.FromObject method uses a different serializer instance internally, so it does not see the DocumentConverter and thus does not get into trouble.

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

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

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

Here is a demo showing the converter in action:

这是一个演示转换器在行动:

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);
    }
}

Here is the output from the above:

这是上面的输出:

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