更改通过JsonExtensionData加载的数据的密钥名称

时间:2017-11-28 10:48:38

标签: json.net

我有一个由系统数据和用户条目混合而成的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完成(我将对象发送到库中),我需要在反序列化期间修复扩展数据名称。

1 个答案:

答案 0 :(得分:2)

<强>更新

由于名称的修复必须在反序列化期间完成,因此您可以LowerCasePropertyNameJsonReaderHow 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(".", ""));

或者,如果您通过StreamStreamReader进行反序列化,则可以直接从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

  • 仅在序列化期间调用命名策略以重新映射扩展数据名称(和字典键),而不是反序列化。

  • 您可能需要cache the contract resolver for best performance

  • 没有内置属性来指定字典的转换器,如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--> 。我对这种不一致没有任何解释;感觉就像一个小虫。