如何使用 Json.Net 反序列化只读列表

问题描述 投票:0回答:6

我有一堂带有内部列表的课程。我不希望此类的用户能够直接与列表交互,因为我希望保持其排序并在返回之前运行计算(这取决于顺序)。

我暴露了

AddItem(Item x) 

和一个

IEnumerable<Item> Items
{
     get { // returns a projection of internal list }
}

序列化工作正常,但反序列化使列表为空。我想这是因为我没有二传手。因此,我添加了一个允许您设置列表的选项,但前提是内部列表为空。但这并没有解决问题,事实证明 NewtonSoft 不会调用 setter,它只调用 getter 来获取列表,然后将每个项目添加到其中,因为我的 getter 返回一个投影列表,所以这些项目被添加到反序列化完成后立即处理的对象。

如何保持对列表的只读访问权限,同时允许进行简单的反序列化?

c# serialization json.net deserialization
6个回答
6
投票

对我有用的是以下内容:

[JsonProperty(PropertyName = "TargetName")]
private List<SomeClass> _SomeClassList { get; set; }
public IReadOnlyList<SomeClass> SomeClassList
{
    get
    {
        return this._SomeClassList.AsReadOnly();
    }
}

然后,创建一个函数来防止 SomeClassList 被序列化:

public bool ShouldSerializeSomeClassList() { return false; }

参见 https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm(感谢 Peska)


4
投票

看起来有很多方法可以做到这一点,但我不想做的一件事是必须修改我的所有数据对象以了解它们应该如何序列化/反序列化。

实现此目的的一种方法是采用 DefaultContractResolver 的其他人所做的一些示例(但仍然没有做我需要做的事情)并修改它们以填充只读字段。

这是我想要序列化/反序列化的类

public class CannotDeserializeThis
{
    private readonly IList<User> _users = new List<User>();
    public virtual IEnumerable<User> Users => _users.ToList().AsReadOnly();

    public void AddUser(User user)
    {
        _users.Add(user);
    }
}

我可以将其序列化为: {"Users":[{"Name":"第一个人"},{"Name":"第二个人"},{"Name":"第三个人"}]}

但是反序列化会使 Users IEnumerable 留空。我能找到的唯一方法是删除 Users 属性上的“.ToList.AsReadonly”或实现 DefaultContractResolver,如下所示:

public class ReadonlyJsonDefaultContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var prop = base.CreateProperty(member, memberSerialization);
        if (!prop.Writable)
        {
            var property = member as PropertyInfo;
            if (property != null)
            {
                var hasPrivateSetter = property.GetSetMethod(true) != null;
                prop.Writable = hasPrivateSetter;

                if (!prop.Writable)
                {
                    var privateField = member.DeclaringType.GetRuntimeFields().FirstOrDefault(x => x.Name.Equals("_" + Char.ToLowerInvariant(prop.PropertyName[0]) + prop.PropertyName.Substring(1)));

                    if (privateField != null)
                    {
                        var originalPropertyName = prop.PropertyName;
                        prop = base.CreateProperty(privateField, memberSerialization);
                        prop.Writable = true;
                        prop.PropertyName = originalPropertyName;
                        prop.UnderlyingName = originalPropertyName;
                        prop.Readable = true;
                    }
                }
            }
        }

        return prop;
    }
}

DefaultContractResolver 正在查找相应的私有支持字段,从中创建一个属性,并将其重命名为公共只读属性。

不过,这是假设有约定的。您的支持字段以下划线开头,并且是您的公共财产的小写版本。对于我们使用的大多数代码来说,这是一个安全的假设。 (例如“用户”->“_users”或“AnotherPropertyName”->“_anotherPropertyName”)


1
投票

使用

Newtonsoft
,您可以使用
CustomCreationConverter<T>
或抽象 JsonConverter,您必须实现
Create
方法和
ReadJson

ReadJson
方法是转换器将调用基本方法进行默认反序列化的地方,从那里,只读集合中的每个项目都可以使用
AddItem
方法进行反序列化和添加。

任何自定义逻辑都可以在 AddItem 中实现。

最后一步是使用属性

[JsonConverter(typeof(NavigationTreeJsonConverter))]
或在
JsonSerializerSettings

中配置此新转换器以进行反序列化
public class ItemsHolderJsonConverter : CustomCreationConverter<ItemsHolder>
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(ItemsHolder).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader,
                                   Type objectType,
                                   object existingValue,
                                   JsonSerializer serializer)
    {

        JObject jObject = JObject.Load(reader);

        ItemsHolder holder = base.ReadJson(CreateReaderFromToken(reader,jObject), objectType, existingValue, serializer) as ItemsHolder;

        var jItems = jObject[nameof(ItemsHolder.Items)] as JArray ?? new JArray();
        foreach (var jItem in jItems)
        {
            var childReader = CreateReaderFromToken(reader, jItem);
            var item = serializer.Deserialize<Item>(childReader);
            holder.AddItem(item);
        }

        return holder;
    }

    public override ItemsHolder Create(Type objectType)
    {
        return new ItemsHolder();
    }

    public static JsonReader CreateReaderFromToken(JsonReader reader, JToken token)
    {
        JsonReader jObjectReader = token.CreateReader();
        jObjectReader.Culture = reader.Culture;
        jObjectReader.DateFormatString = reader.DateFormatString;
        jObjectReader.DateParseHandling = reader.DateParseHandling;
        jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
        jObjectReader.FloatParseHandling = reader.FloatParseHandling;
        jObjectReader.MaxDepth = reader.MaxDepth;
        jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
        return jObjectReader;
    }
}

1
投票

似乎最近版本的 Newtonsoft.Json 会自动将

interface IReadOnlyList<T>
反序列化为
class ReadOnlyCollection<T>

我正在使用

Newtonsoft.Json
版本 13.0.1,该程序在 Linqpad 7 中运行良好:

void Main()
{
    const String JSON = @"
    {
        ""propertyName"": [
            123,
            456
        ]
    }
    ";
    
    Dto dto = JsonConvert.DeserializeObject<Dto>( JSON );
    
    String x = "foo";
}

public class Dto
{
    [JsonConstructor]
    public Dto(
        [JsonProperty( "propertyName" )] IReadOnlyList<Int32> propertyName
    )
    {
        this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
    }
    
    public IReadOnlyList<Int32> PropertyName { get; }
}

截图证明:

enter image description here


0
投票

我在 Stackoverflow 上的评论部分偶然发现了答案,但它没有被投票。而且,我在这里给你更详细的答案:

public class State
{
        [Newtonsoft.Json.JsonProperty]
        public double Citizens { get; private set; }

        [Newtonsoft.Json.JsonProperty]
        public float Value { get { return pValue; } }
        private float pValue = 450000.0f;

        public List<string> BeachList { get; } = new List<string>();

    public State()
    {
    }

    public State(double _Citizens)
    {
        this.Citizens = _Citizens;
    }
}

...

            State croatia = new State(30.0D);
            croatia.BeachList.Add("Bol na Braču");
            croatia.BeachList.Add("Zrće");

           string croatiaSerialized = Newtonsoft.Json.JsonConvert.SerializeObject(croatia);

           State slovenia = Newtonsoft.Json.JsonConvert.DeserializeObject<State>(croatiaSerialized);

因此,克罗地亚和斯洛文尼亚现在都具有相同的财产价值。 我添加了 Citizens 和 Value 属性,看看您是否想使用其中一种方式。

感谢 Saeb Amini(Json.Net 中的私人设置者


0
投票

就我而言,我只是将 System.Text.Json 库替换为 Newtonsoft.Json,不需要更改任何内容或创建任何转换器类。

© www.soinside.com 2019 - 2024. All rights reserved.