How to deserialize read-only List with Json.Net

I have a class with an internal list. I don't want the user of this class to be able to directly interact with this list, as I want it to sort and perform a calculation (which is order dependent) before returning it.

I exhibit

AddItem(Item x) 

      

and a

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

      

Serialization worked fine, but deserialization left the list empty. I figured it was because I didn't have a setter. So I added one that allowed you to set the list, but only if the inner list is empty. But that didn't solve the problem, it turned out that NewtonSoft does not call the setter, it only calls the getter to get the list, and then adds each item to it, which, since my getter returns a projected list, these items are added to the object that is immediately located upon completion deserialization.

How do I keep read-only access to my list and at the same time allow somewhat direct deserialization?

+4


source to share


5 answers


The following worked for me:

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

      

Then create a function to prevent SomeClassList from serializing:



public bool ShouldSerializeSomeClassList() { return false; }

      

See https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm (thanks to Sand)

+4


source


It looks like there are multiple ways to do this, but I didn't want to have to change all of my data objects to know how to serialize / deserialize them.

One way to do this was to take some of the DefaultContractResolver examples that others did (but still didn't do what I needed to do) and modify them to fill read-only fields.

Here is my class that I would like to serialize / deserialize

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);
    }
}

      

I could serialize this to: {"Users": [{"Name": "First guy"}, {"Name": "Second guy"}, {"Name": "Third guy"}]}



But on deserialization, this will leave "Users" empty. The only way I could find is to either remove ".ToList.AsReadonly" in the Users property, or implement the DefaultContractResolver as such:

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;
    }
}

      

The DefaultContractResolver finds the corresponding private helper field, creates a property from it, and renames it to a read-only public property.

It does suggest an agreement, though. That your helper field starts with an underscore and is a lowercase version of your public property. For most of the code we worked with, this was a safe assumption. (for example, "Users" β†’ "_users" or "AnotherPropertyName" β†’ "_anotherPropertyName")

+2


source


With, Newtonsoft

you can use CustomCreationConverter<T>

either the abstract JsonConverter, you need to implement the method Create

and ReadJson

.

Method ReadJson

is where the converter will do the default deserialization calling the base method, from there every item inside the readonly collection can be deserialized and added using the method AddItem

.

Any custom logic can be implemented inside AddItem.

The last step is to configure this new converter to deserialize with [JsonConverter(typeof(NavigationTreeJsonConverter))]

or inside the attributeJsonSerializerSettings

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


source


I stumbled upon an answer on Stackoverflow in the comment section, but it hasn't been upvoted. And I give you a more detailed answer here:

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);

      

So, Croatia and Slovenia now have the same property values. I've added Citizens and Properties to see if you want to work with one way or another.

Thanks to Saeb Amini ( Private setters in Json.Net )

0


source


It is now 2017 and I can report that Json.NET 10.x seems to be correct. Here's my test case:

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>();
}

      

When I run this, the dumps p

and are p2

identical.

-1


source







All Articles