我有一个内部列表的类。我不希望这个类的用户能够直接与列表进行交互,因为我希望在返回之前对其进行排序并运行计算(这取决于顺序)。
我曝光
AddItem(Item x)
和
IEnumerable<Item> Items
{
get { // returns a projection of internal list }
}
序列化工作正常,但反序列化将列表留空。我认为这是因为我没有一个二传手。所以我添加了一个允许你设置列表的内容,但前提是内部列表为空。但这并没有解决问题,原来NewtonSoft没有调用setter,它只调用getter来获取列表,然后将每个项目添加到它,因为我的getter返回一个预测列表,那些项目添加到反序列化完成后立即处理的对象。
如何保持对列表的只读访问权限,同时允许稍微简单的反序列化?
答案 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>();
}
运行此操作时,p
和p2
的转储完全相同。
答案 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)