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

时间:2014-12-09 19:20:50

标签: c# serialization json.net deserialization

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

我曝光

AddItem(Item x) 

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

序列化工作正常,但反序列化将列表留空。我认为这是因为我没有一个二传手。所以我添加了一个允许你设置列表的内容,但前提是内部列表为空。但这并没有解决问题,原来NewtonSoft没有调用setter,它只调用getter来获取列表,然后将每个项目添加到它,因为我的getter返回一个预测列表,那些项目添加到反序列化完成后立即处理的对象。

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

5 个答案:

答案 0 :(得分:2)

对我有用的是:

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

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

public bool ShouldSerializeSomeClassList() { return false; }

答案 1 :(得分:1)

现在是2017年,我可以报告Json.NET 10.x似乎是正确的。这是我的测试用例:

void Main()
{
    Poco p = new Poco();
    p.Items.Add("foo");
    p.Items.Add("bar");

    p.Dump(); // Linqpad command to show a visual objet dump in the output pane

    String serialized = Newtonsoft.Json.JsonConvert.SerializeObject( p );

    serialized.Dump();
    // serialized == "{"Items":["foo","bar"]}"

    Poco p2 = (Poco)Newtonsoft.Json.JsonConvert.DeserializeObject( serialized, typeof(Poco) );

    p2.Dump();
}

class Poco
{
    public List<String> Items { get; } = new List<String>();
}

运行此操作时,pp2的转储完全相同。

答案 2 :(得分:1)

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

ReadJson方法是转换器执行默认反序列化调用基本方法的地方,从那里,readonly集合中的每个项都可以反序列化并使用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;
    }
}

答案 3 :(得分:1)

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

一种方法是采用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”:“ First Guy”},{“ Name”:“ Second Guy”},{“ Name”:“ Third Guy”}]}

但是反序列化将使用户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”)

答案 4 :(得分: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(Private setters in Json.Net