Json.NET just didn't implement read-only reference collections and arrays. This is explicitly stated in the exception message:
Newtonsoft.Json.JsonSerializationException: Unable to save reference for array or read-only list or list created from custom constructor: Question41293407.Test [].
The reason Newtonsoft hasn't implemented this is because their link tracking features are designed to preserve recursive links to themselves. Thus, an object that has been deserialized must be selected before reading its contents, so that nested backlinks can be successfully resolved during deserialization of the content. However, a read-only collection can only be assigned after its contents have been read, since by definition it is read-only.
Arrays, however, are peculiar in that they are only βsemiβ read-only: they cannot be changed after allocation, however, individual records can be changed. (See Array.IsReadOnly, which is not compatible depending on the implementation of the interface , for a discussion of this issue.) You can use this fact to create a custom JsonConverter for arrays that load JSON into an intermediate JToken while reading, allocate an array of the correct size, requesting the contents of the token , adds an array to serializer.ReferenceResolver , deserializes the contents into a list, and then fills the array entries from the list:
public class ArrayReferencePreservngConverter : JsonConverter { const string refProperty = "$ref"; const string idProperty = "$id"; const string valuesProperty = "$values"; public override bool CanConvert(Type objectType) { return objectType.IsArray; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; else if (reader.TokenType == JsonToken.StartArray) {
The memory efficiency of this approach is low, so for large collections it would be better to switch to List<T> .
Then use it like:
var settings = new JsonSerializerSettings { Converters = { new ArrayReferencePreservngConverter() }, PreserveReferencesHandling = PreserveReferencesHandling.All }; var a2 = JsonConvert.DeserializeObject<Test[]>(jsonString, settings);
Please note that the converter is completely general and works for all arrays.
Fiddle example showing successful deserialization of nested recursive self-advertisements.