问题
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.
回答1:
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 from CamelCaseNamingStrategy 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.
来源:https://stackoverflow.com/questions/47529655/change-key-name-of-data-loaded-through-jsonextensiondata