更改通过JsonExtensionData加载的数据的键名 [英] Change key name of data loaded through JsonExtensionData

查看:88
本文介绍了更改通过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(".", ""));

或者,如果要通过StreamReaderStream反序列化,则可以直接从中构造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都不会使用应用于OtherPropertiesJsonConverter,因为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(). github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs#L335"rel =" nofollow noreferrer>此处,但在命名策略时使用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 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.

这篇关于更改通过JsonExtensionData加载的数据的键名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆