使用自定义JsonConverter来改变对象部分的序列化

我很难重写自定义JsonConverter的WriteJson方法,以便稍微改变序列化的执行方式。

我需要调用REST服务,该服务接受具有通用部分的特定输入。 我可以使用以下有效内容格式重现我遇到的问题:

public sealed class JsonField { public string Key { get; set; } public object Value { get; set; } public string OtherParam { get; set; } } public sealed class JsonPayload { public string Item1 { get; set; } public string Item2 { get; set; } public List Fields { get; set; } } 

我正在调用的REST API需要将Fields作为一个对象,该对象包含与原始集合中指定的Key属性对应的名称的字段。 像这样:

 { "Item1" : "Value1", "Item2" : "Value2", ... "Fields": { "Key1": {"Value":"Value1", "OtherParam":"other1"} "Key2": {"Value":42, "OtherParam":"other2"} } } 

但是,使用默认选项,有效负载序列化如下:

 { "Item1" : "Value1", "Item2" : "Value2", ... "Fields":[ { "Key":"Key1", "Value":"Value1", "OtherParam":"other1" } { "Key":"Key2", "Value":42, "OtherParam":"other2" } ] } 

你会注意到我想要一个对象的对象集合。 另外,我希望键名是Fields中各个属性的名称。

我很难弄清楚如何在我的自定义转换器中使用JToken,JProperty,JValue对象。 事实上,我从来没有尝试过这样的事情,所以我发现很难围绕这些概念。

我尝试创建两个自定义转换器,一个在类的范围内,以防止生成集合,第二个在集合的范围内,但到目前为止没有成功。

这是我尝试过的:

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var token = JToken.FromObject(value); var key = token["Key"]; if (key == null) throw new Exception("missing key"); var propertyName = key.ToString(); var json = new StringBuilder(); json.Append("{"); foreach (var child in token.Children()) { var property = child as JProperty; if (property == null || property.Name == "Key") continue; var propName = property.Name; var propValue = JsonConvert.SerializeObject(property.Value); json.AppendFormat("\"{0}\": {1},", propName, propValue); } if (json.Length > 1) json.Remove(json.Length - 1, 1); json.Append("}"); var newToken = JToken.Parse(json.ToString()); var serializedObject = JsonConvert.SerializeObject(newToken); writer.WriteStartObject(); writer.WritePropertyName(propertyName); writer.WriteToken(newToken.CreateReader()); writer.WriteEndObject(); } 

有没有办法实现我想要实现的目标?

也许我正在以错误的方式解决问题,所以,如果你有更容易的选择,那么请务必分享你的想法。

你走在正确的轨道上。 这里只需要一个转换器 – 对于JsonField对象列表 – 代码实际上非常简单。 这就是你所需要的:

 class JsonFieldListConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(List)); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { JObject containerObj = new JObject(); foreach (JsonField field in (List)value) { JObject itemObj = new JObject(); itemObj.Add("Value", JToken.FromObject(field.Value)); itemObj.Add("OtherParam", new JValue(field.OtherParam)); containerObj.Add(field.Key, itemObj); } containerObj.WriteTo(writer); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } } 

这是一个演示转换器运行的演示:

 class Program { static void Main(string[] args) { JsonPayload payload = new JsonPayload { Item1 = "Value1", Item2 = "Value2", Fields = new List { new JsonField { Key = "Key1", Value = "Value1", OtherParam = "other1" }, new JsonField { Key = "Key2", Value = 42, OtherParam = "other2" }, } }; JsonSerializerSettings settings = new JsonSerializerSettings(); settings.Converters.Add(new JsonFieldListConverter()); settings.Formatting = Formatting.Indented; string json = JsonConvert.SerializeObject(payload, settings); Console.WriteLine(json); } } 

输出:

 { "Item1": "Value1", "Item2": "Value2", "Fields": { "Key1": { "Value": "Value1", "OtherParam": "other1" }, "Key2": { "Value": 42, "OtherParam": "other2" } } } 

简短回答:

您只需将List更改为Dictionary

回答一个例子:

您可以通过将List更改为Dictionary此操作,这将为您提供看似单个对象的属性名称作为字典的键。 如果类包含密钥,那么它也将被序列化,尽管你可以[JsonIgnore]它,但这意味着它不会在反序列化期间填充。

 void Main() { var pl = new JsonPayload() {Item1 = "Value1",Item2 = "Value2"}; var field1 = new JsonField() { Key = "Key1", Value = "Value1", OtherParam = "other1" }; var field2 = new JsonField() { Key = "Key2", Value = "Value2", OtherParam = "other2" }; pl.Fields = new Dictionary() { { field1.Key , field1}, { field2.Key, field2 }}; string json = JsonConvert.SerializeObject(pl); JsonPayload pl2 = JsonConvert.DeserializeObject(json); string output = JsonConvert.SerializeObject(pl2); output.Dump(); } public sealed class JsonField { public string Key { get; set; } public object Value { get; set; } public string OtherParam { get; set; } } public sealed class JsonPayload { public string Item1 { get; set; } public string Item2 { get; set; } public Dictionary Fields { get; set; } }