更改通过JsonExtensionData加载的数据的键名 [英] Change key name of data loaded through JsonExtensionData
问题描述
我有一个混合使用系统数据和用户条目生成的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完成的(我将对象发送到库中),因此我需要在反序列化期间固定扩展数据名称.
更新
由于名称的固定必须在反序列化期间完成,因此您可以通过如何更改来概括LowerCasePropertyNameJsonReader
Brian Rogers 将JSON解析为JToken 时所有小写的键,以执行必要的转换. /p>
首先,定义以下内容:
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
(如果您愿意). -
仅在序列化而不是反序列化期间调用命名策略以重新映射扩展数据名称(和字典键).
-
您可能想要缓存合同解析器以获得最佳性能.
-
没有内置属性为字典 keys 指定转换器,如这个问题.而且无论如何,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
在使用通过属性GetExtensionDataName()
通过设置此处.对于不一致之处,我没有任何解释;感觉像个虫子.
I have a JSON resulting from a mix of system data and user entries, like this :
{
"Properties": [{
"Type": "A",
"Name": "aaa",
"lorem ipsum": 7.1
}, {
"Type": "B",
"Name": "bbb",
"sit amet": "XYZ"
}, {
"Type": "C",
"Name": "ccc",
"abcd": false
}]
}
I need to load it, process it, and save it to MongoDB. I deserialize it to this class :
public class EntityProperty {
public string Name { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> OtherProperties { get; set; }
public string Type { get; set; }
}
The problem is that MongoDB does not allow dots in key names, but the users can do whatever they want.
So I need a way to save this additional JSON data but I also need to change the key name as it's being processed.
I tried to add [JsonConverter(typeof(CustomValuesConverter))]
to the OtherProperties
attribute but it seems to ignore it.
Update/Clarification: since the serialization is done by Mongo (I send the objects to the library), I need the extension data names to be fixed during deserialization.
Update
Since the fixing of names must be done during deserialization, you could generalize the LowerCasePropertyNameJsonReader
from How to change all keys to lowercase when parsing JSON to a JToken by Brian Rogers to perform the necessary transformation.
First, define the following:
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);
}
}
}
Then deserialize as follows:
var root = JsonExtensions.DeserializeObject<RootObject>(json, (s) => s.Replace(".", ""));
Or, if you are deserializing from a Stream
via a StreamReader
you can construct your PropertyNameMappingJsonReader
directly from it.
Sample fiddle.
Alternatively, you could also fix the extension data in an [OnDeserialized]
callback, but I think this solution is neater because it avoids adding logic to the objects themselves.
Original Answer
Assuming you are using Json.NET 10.0.1 or later, you can create your own custom NamingStrategy
, override NamingStrategy.GetExtensionDataName()
, and implement the necessary fix.
First, define MongoExtensionDataSettingsNamingStrategy
as follows:
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(".", "");
}
}
Then serialize your root object as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver { NamingStrategy = new MongoExtensionDataSettingsNamingStrategy() },
};
var outputJson = JsonConvert.SerializeObject(root, settings);
Notes:
Here I am inheriting from
DefaultNamingStrategy
but you could inherit fromCamelCaseNamingStrategy
if you prefer.The naming strategy is only invoked to remap extension data names (and dictionary keys) during serialization, not deserialization.
You may want to cache the contract resolver for best performance.
There is no built-in attribute to specify a converter for dictionary keys, as noted in this question. And in any event Json.NET would not use the
JsonConverter
applied toOtherProperties
since the presence of theJsonExtensionData
attribute supersedes the converter property.
Alternatively, if it would be more convenient to specify the naming strategy using Json.NET serialization attributes, you will need a slightly different naming strategy. First create:
public class MongoExtensionDataAttributeNamingStrategy : MongoExtensionDataSettingsNamingStrategy
{
public MongoExtensionDataAttributeNamingStrategy()
: base()
{
this.ProcessDictionaryKeys = true;
}
public override string GetDictionaryKey(string key)
{
if (!ProcessDictionaryKeys)
{
return key;
}
return FixName(key);
}
}
And modify EntityProperty
as follows:
[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; }
}
The reason for the inconsistency is that, as of Json.NET 10.0.3, DefaultContractResolver
uses GetDictionaryKey()
when remapping extension data names using a naming strategy that is set via attributes here, but uses GetExtensionDataName()
when the naming strategy is set via settings here. I have no explanation for the inconsistency; it feels like a bug.
这篇关于更改通过JsonExtensionData加载的数据的键名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!