Change key name of data loaded through JsonExtensionData [duplicate]

痞子三分冷 提交于 2021-01-28 10:10:48

问题


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 to OtherProperties since the presence of the JsonExtensionData 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!