我有一个由系统数据和用户条目混合而成的JSON,如下所示:
{
"Properties": [{
"Type": "A",
"Name": "aaa",
"lorem ipsum": 7.1
}, {
"Type": "B",
"Name": "bbb",
"sit amet": "XYZ"
}, {
"Type": "C",
"Name": "ccc",
"abcd": false
}]
}
我需要加载它,处理它并将其保存到MongoDB。我把它反序列化到这个类:
public class EntityProperty {
public string Name { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> OtherProperties { get; set; }
public string Type { get; set; }
}
问题是MongoDB不允许键名中的点,但用户可以做任何他们想做的事。
所以我需要一种方法来保存这些额外的JSON数据,但我还需要在处理时更改密钥名称。
我尝试将[JsonConverter(typeof(CustomValuesConverter))]
添加到OtherProperties
属性,但它似乎忽略了它。
更新/澄清:由于序列化由Mongo完成(我将对象发送到库中),我需要在反序列化期间修复扩展数据名称。
答案 0 :(得分:2)
<强>更新强>
由于名称的修复必须在反序列化期间完成,因此您可以LowerCasePropertyNameJsonReader
从How to change all keys to lowercase when parsing JSON to a JToken推广Brian Rogers以执行必要的转换。
首先,定义以下内容:
public class PropertyNameMappingJsonReader : JsonTextReader
{
readonly Func<string, string> nameMapper;
public PropertyNameMappingJsonReader(TextReader textReader, Func<string, string> nameMapper)
: base(textReader)
{
if (nameMapper == null)
throw new ArgumentNullException();
this.nameMapper = nameMapper;
}
public override object Value
{
get
{
if (TokenType == JsonToken.PropertyName)
return nameMapper((string)base.Value);
return base.Value;
}
}
}
public static class JsonExtensions
{
public static T DeserializeObject<T>(string json, Func<string, string> nameMapper, JsonSerializerSettings settings = null)
{
using (var textReader = new StringReader(json))
using (var jsonReader = new PropertyNameMappingJsonReader(textReader, nameMapper))
{
return JsonSerializer.CreateDefault(settings).Deserialize<T>(jsonReader);
}
}
}
然后按如下方式反序列化:
var root = JsonExtensions.DeserializeObject<RootObject>(json, (s) => s.Replace(".", ""));
或者,如果您通过Stream
从StreamReader
进行反序列化,则可以直接从PropertyNameMappingJsonReader
构建MongoExtensionDataSettingsNamingStrategy
。
示例fiddle。
或者,您也可以在[OnDeserialized]
回调中修复扩展数据,但我认为此解决方案更简洁,因为它避免了向对象本身添加逻辑。
原始答案
假设您使用的是Json.NET 10.0.1 or later,您可以创建自己的自定义NamingStrategy
,覆盖NamingStrategy.GetExtensionDataName()
,并实施必要的修复。
首先,按如下方式定义public class MongoExtensionDataSettingsNamingStrategy : DefaultNamingStrategy
{
public MongoExtensionDataSettingsNamingStrategy()
: base()
{
this.ProcessExtensionDataNames = true;
}
protected string FixName(string name)
{
return name.Replace(".", "");
}
public override string GetExtensionDataName(string name)
{
if (!ProcessExtensionDataNames)
{
return name;
}
return name.Replace(".", "");
}
}
:
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver { NamingStrategy = new MongoExtensionDataSettingsNamingStrategy() },
};
var outputJson = JsonConvert.SerializeObject(root, settings);
然后按如下方式序列化根对象:
DefaultNamingStrategy
注意:
此处我继承自JsonConverter
,但如果您愿意,可以继承CamelCaseNamingStrategy
。
仅在序列化期间调用命名策略以重新映射扩展数据名称(和字典键),而不是反序列化。
没有内置属性来指定字典键的转换器,如this question中所述。无论如何,Json.NET不会使用OtherProperties
应用于JsonExtensionData
,因为public class MongoExtensionDataAttributeNamingStrategy : MongoExtensionDataSettingsNamingStrategy
{
public MongoExtensionDataAttributeNamingStrategy()
: base()
{
this.ProcessDictionaryKeys = true;
}
public override string GetDictionaryKey(string key)
{
if (!ProcessDictionaryKeys)
{
return key;
}
return FixName(key);
}
}
属性的存在会取代转换器属性。
或者,如果使用Json.NET serialization attributes指定命名策略会更方便,则需要稍微不同的命名策略。首先创建:
EntityProperty
按如下方式修改[JsonObject(NamingStrategyType = typeof(MongoExtensionDataAttributeNamingStrategy))]
public class EntityProperty
{
public string Name { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> OtherProperties { get; set; }
public string Type { get; set; }
}
:
DefaultContractResolver
不一致的原因是,从Json.NET 10.0.3开始,GetDictionaryKey()
在使用通过属性here设置的命名策略重新映射扩展数据名称时使用GetExtensionDataName()
,但通过设置here设置命名策略时使用<!--region Title-->
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!--endregion-->
。我对这种不一致没有任何解释;感觉就像一个小虫。