我正在将JSON序列化以包含在搜索索引(Elasticsearch)中。
我正在序列化的一个属性是记录器状态,它可以包含任何对象。我限制了递归的深度并处理序列化错误。然而,我遇到的问题是,不同的对象/类型可能具有相同但具有不同类型的成员。
例如,我可能会将{ scope: { query: "?hello=true" } }
序列化为一种类型,将{ scope: { query: { userId: 123 } } }
序列化为另一种类型。
Elasticsearch不喜欢这样,所以我的想法是,对于用户定义的结果对象的部分,我将使用不同的属性名称,因此它们各自获得自己的索引字段,对应于它们的类型。
我正在考虑将上述两个例子翻译成:
{ scope: { query_str: "?hello=true" } }
和{ scope: { query_obj: { userId_num: 123 } } }
或
{ scope: { query: { str: "?hello=true" } } }
和{ scope: { query: { obj: { userId: { num: 123 } } } } }
几乎可以肯定前者..但是,在Json.NET内部似乎没有办法根据运行时的属性值改变属性名称。
我无法覆盖内部序列化编写器的对象和字典序列化,似乎没有一个扩展点来执行此操作。
我想到的方法:
JsonTextWriter
,查看Path
等以使更改处于较低级别似乎没什么好看的。什么是我最好的选择?
编辑:测试用例 -
public class A
{
public int Num { get; set; }
}
[Fact]
public void JsonFieldNamesMangled()
{
var entry = new Dictionary<string, object>()
{
{ "abc", "def" },
{
"scope", new
{
SomeString1 = "hello",
SomeInt1 = 12,
SomeArr1 = new[] { 1, 2 }
}
}
};
entry["state"] = new Dictionary<string, object>
{
{ "SomeString2", "goodbye" },
{ "SomeInt2", 34 },
{ "SomeArr2", new[] { new A { Num = 1 }, new A { Num = 2 } } }
};
var json = JsonConvert.SerializeObject(entry);
// original: "{\"abc\":\"def\",\"scope\":{\"SomeString1\":\"hello\",\"SomeInt1\":12,\"SomeArr1\":[1,2]},\"state\":{\"SomeString2\":\"goodbye\",\"SomeInt2\":34,\"SomeArr2\":[{\"Num\":1},{\"Num\":2}]}}");
json.Should().Be("{\"abc\":\"def\",\"scope\":{\"SomeString1_str\":\"hello\",\"SomeInt1_num\":12,\"SomeArr1_arr_num\":[1,2]},\"state\":{\"SomeString2_str\":\"goodbye\",\"SomeInt2_num\":34,\"SomeArr2_arr_obj\":[{\"Num_num\":1},{\"Num_num\":2}]}}");
}
答案 0 :(得分:1)
一个选项是实现自己的custom JsonConverter
,其WriteJson()
方法重新实现JsonSerializerInternalWriter.SerializeObject()
逻辑的一些最小必要子集,同时根据属性值重新映射属性名称类型:
public abstract class JsonObjectPropertyNameRemappingConverterBase : JsonConverter
{
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected abstract string ResolvePropertyName(JsonSerializer serializer, JsonProperty property, object value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties)
{
if (!property.Readable || property.Ignored || (property.ShouldSerialize != null && !property.ShouldSerialize(value)))
continue;
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
continue;
//Todo if required:
//serializer.DefaultValueHandling, serializer.PreserveReferencesHandling, serializer.ReferenceLoopHandling, serializer.TypeNameHandling
//property.Converter, property.ItemConverter
var name = ResolvePropertyName(serializer, property, propertyValue);
writer.WritePropertyName(name);
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndObject();
}
}
public class JsonObjectPropertyNameRemappingConverter : JsonObjectPropertyNameRemappingConverterBase
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should be applied via [JsonConverter(typeof(JsonObjectPropertyNameRemappingConverter)]");
}
protected override string ResolvePropertyName(JsonSerializer serializer, JsonProperty property, object value)
{
// Replace with whatever logic you want here.
if (value == null)
return property.PropertyName;
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
string type;
if (contract is JsonObjectContract)
type = "obj";
else if (contract is JsonArrayContract)
type = "array";
else if (value is string)
type = "str";
else if (value is Int32)
type = "num";
else
type = value.GetType().Name.ToLowerInvariant();
return property.PropertyName + "_" + type;
}
}
public class JsonObjectPropertyNameRemappingConverter<T> : JsonObjectPropertyNameRemappingConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
}
然后你就可以将它应用到你的模型中:
[JsonConverter(typeof(JsonObjectPropertyNameRemappingConverter))]
public class Scope
{
public object Query { get; set; }
}
public class RootObject
{
public Scope Scope { get; set; }
}
[JsonConverter(typeof(JsonObjectPropertyNameRemappingConverter))]
class Id
{
public int UserId { get; set; }
}
或者在如此设置中应用它:
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
Converters = { new JsonObjectPropertyNameRemappingConverter<Scope>(), new JsonObjectPropertyNameRemappingConverter<Id>() },
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
注意:
转换器实现选项#1。
转换器使用Json.NET的contract resolver来执行所有必要的反射。这简化了逻辑,自动支持骆驼外壳,并且应该通过缓存反射元数据来提高性能。
转换器不支持实现ISerializable
的词典和类型。
ReadJson()
未实施(目前尚不清楚如何实施。)
工作样本.Net小提琴here。