Skip to content

ReferenceHandler.IgnoreCycles doesn't work with Custom Converters #51715

Closed

Description

I need to add $type property for an array that will contain objects of different types ex : ( [A,B,C] of IEnumerable< A> => [A,{$type:"B",$value:B},{$type:"C",$value:C}] )
My custom JsonConverter will have a JsonSerializer.Serialize in the Write method but that resets the state of the serialization so It can no more check the cycles :

        private abstract class AbstractEnumerableJsonConverter<TObj, TCollection> :
            JsonConverter<TCollection> where TCollection: IEnumerable<TObj>
        {


            public override TCollection? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                var list = new List<TObj>();
                while (reader.Read())
                {
                    if (reader.TokenType == JsonTokenType.EndArray)
                    {
                        break;
                    }
                    if (reader.TokenType == JsonTokenType.StartObject)
                    {
                        var deserializedObj = default(TObj);
                        var document = JsonDocument.ParseValue(ref reader);
                        if (document.RootElement.TryGetProperty("$type", out var typeName))
                        {
                            var type = Type.GetType(typeName.GetString());
                            if (type.IsAssignableTo(typeof(IDbo)))
                                deserializedObj = (TObj)JsonSerializer.Deserialize(document.RootElement.GetProperty("$value").GetRawText(), type, options);
                        }
                        deserializedObj ??= (TObj)JsonSerializer.Deserialize(document.RootElement.ToString(), typeof(TObj), options);
                        list.Add(deserializedObj);
                    }
                }
                var ret = (IList)Activator.CreateInstance(typeToConvert, list.Count);
                if (ret.Count == 0)
                    for (var i = 0; i < list.Count; ++i)
                        ret.Add(list[i]);
                else
                    for (var i = 0; i < ret.Count; ++i)
                        ret[i] = list[i];

                return (TCollection)ret;
            }
            
            public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
            {
                writer.WriteStartArray();
                foreach (var obj in value)
                {
                    if( obj.GetType() != typeof(TObj))
                    {
                        writer.WriteStartObject();
                        writer.WriteString("$type", obj.GetType().FullName);
                        writer.WritePropertyName("$value");
                        JsonSerializer.Serialize(writer, obj, options);   //<---- The state is lost so we can no more check cycles**
                        writer.WriteEndObject();
                    }
                    else
                    JsonSerializer.Serialize(writer, obj, options);       //<---- The state is lost so we can no more check cycles**
                }
                writer.WriteEndArray();
            }
        }

I have tried to do something like dotnet/docs#21777 (comment) , but it seems that IgnoreCycles relies on a lot of things that are internal and resides in the Core code ( like PushReferenceForCycleDetection that maybe would help if it's protected and not internal

internal override void PushReferenceForCycleDetection(object value)
).

Is there any way I can get around these cycles?

(I have also tried to serialize the properties of the object one by one to avoid calling JsonSerializer.Serialize, but the code to enumerate one object's properties is also internal )

Originally posted by @YeskaNova in dotnet/docs#21777 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    area-System.Text.JsonenhancementProduct code improvement that does NOT require public API changes/additions

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions