>我有一个由系统数据和用户条目混合生成的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 完成的(我将对象发送到库),我需要在反序列化期间修复扩展数据名称。
更新
由于名称的修复必须在反序列化期间完成,因此您可以将 Brian Rogers 的 JSON 解析为 JToken 时如何将所有键更改为小写LowerCasePropertyNameJsonReader
概括为小写,以执行必要的转换。
首先,定义以下内容:
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(".", ""));
或者,如果要通过StreamReader
从Stream
反序列化,则可以直接从PropertyNameMappingJsonReader
构造。
样品小提琴。
或者,您也可以在[OnDeserialized]
回调中修复扩展数据,但我认为这个解决方案更整洁,因为它避免了向对象本身添加逻辑。
原始答案
假设您使用的是 Json.NET 10.0.1 或更高版本,则可以创建自己的自定义NamingStrategy
、覆盖NamingStrategy.GetExtensionDataName()
并实施必要的修复。
首先,按如下方式定义MongoExtensionDataSettingsNamingStrategy
:
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
但如果你愿意,你可以从CamelCaseNamingStrategy
继承。命名策略仅用于在序列化期间重新映射扩展数据名称(和字典键),而不是反序列化。
您可能希望缓存协定解析程序以获得最佳性能。
没有内置属性来指定字典键的转换器,如本问题中所述。 无论如何,Json.NET 不会使用应用于
OtherProperties
的JsonConverter
,因为JsonExtensionData
属性的存在会取代转换器属性。
或者,如果使用序列化属性指定命名策略更方便 Json.NET 则需要稍微不同的命名策略。 首先创建:
public class MongoExtensionDataAttributeNamingStrategy : MongoExtensionDataSettingsNamingStrategy
{
public MongoExtensionDataAttributeNamingStrategy()
: base()
{
this.ProcessDictionaryKeys = true;
}
public override string GetDictionaryKey(string key)
{
if (!ProcessDictionaryKeys)
{
return key;
}
return FixName(key);
}
}
并按如下方式修改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; }
}
不一致的原因是,从 Json.NET 10.0.3 开始,DefaultContractResolver
使用此处的属性设置的命名策略重新映射扩展数据名称时使用GetDictionaryKey()
,但在通过此处的设置设置命名策略时GetExtensionDataName()
使用。 我对这种不一致没有任何解释;感觉就像一个错误。