Skip to content

JsonSerializer cannot serialize types that implement IDictionary<string, object> #29822

Description

@davidfowl

I was trying to serialize the event counter payload (https://github.com/dotnet/coreclr/blob/25878db0c11bfb8d52088c75ccfcc13e4c8383f2/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs#L28) which is a type that implements IDictionary<string, object> and it failed with:

Unhandled exception. System.Diagnostics.Tracing.EventSourceException: An error occurred when writing to a listener. ---> System.InvalidCastException: Unable to cast object of type 'System.Diagnostics.Tracing.EventPayload' to type 'System.Collections.IDictionary'.
   at System.Text.Json.JsonSerializer.HandleDictionary(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.ToString(Object value, Type type, JsonSerializerOptions options)
   at WebApplication275.MyListener.OnEventWritten(EventWrittenEventArgs eventData) in C:\Users\david\source\repos\WebApplication275\WebApplication275\Startup.cs:line 61
   at System.Diagnostics.Tracing.EventSource.DispatchToAllListeners(Int32 eventId, Guid* childActivityID, EventWrittenEventArgs eventCallbackArgs)
   --- End of inner exception stack trace ---
   at System.Diagnostics.Tracing.EventSource.DispatchToAllListeners(Int32 eventId, Guid* childActivityID, EventWrittenEventArgs eventCallbackArgs)
   at System.Diagnostics.Tracing.EventSource.WriteToAllListeners(String eventName, EventDescriptor& eventDescriptor, EventTags tags, Guid* pActivityId, Guid* pChildActivityId, EventPayload payload)
   at System.Diagnostics.Tracing.EventSource.WriteImpl(String eventName, EventSourceOptions& options, Object data, Guid* pActivityId, Guid* pRelatedActivityId, TraceLoggingEventTypes eventTypes)
   at System.Diagnostics.Tracing.PollingCounter.WritePayload(Single intervalSec, Int32 pollingIntervalMillisec)
   at System.Diagnostics.Tracing.CounterGroup.OnTimer(Object state)
   at System.Diagnostics.Tracing.CounterGroup.<>c.<EnableTimer>b__15_0(Object s)
   at System.Threading.TimerQueueTimer.CallCallback(Boolean isThreadPool)
   at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
   at System.Threading.TimerQueue.FireNextTimers()
   at System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)
Unhandled exception. System.Diagnostics.Tracing.EventSourceException: An error occurred when writing to a listener. ---> System.InvalidCastException: Unable to cast object of type 'System.Diagnostics.Tracing.EventPayload' to type 'System.Collections.IDictionary'.
   at System.Text.Json.JsonSerializer.HandleDictionary(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state)
   at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.ToString(Object value, Type type, JsonSerializerOptions options)
   at WebApplication275.MyListener.OnEventWritten(EventWrittenEventArgs eventData) in C:\Users\david\source\repos\WebApplication275\WebApplication275\Startup.cs:line 61

I then tried do to this:

var value = JsonSerializer.ToString(eventData.Payload[0], typeof(IDictionary<string, object>));

But it still failed.

Seems like this should work, here's a full repro:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.Json;

namespace WebApplication275
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var values = new DictionaryWrapper(new Dictionary<string, object>
            {
                { "Name", "David" },
                { "Age", 32 }
            });

            var value = JsonSerializer.ToString(values, typeof(IDictionary<string, object>));

            Console.WriteLine(value);
        }
    }

    public class DictionaryWrapper : IDictionary<string, object>
    {
        private readonly IDictionary<string, object> _dict;

        public DictionaryWrapper(IDictionary<string,object> dict)
        {
            _dict = dict;
        }

        public object this[string key] { get => _dict[key]; set => _dict[key] = value; }

        public ICollection<string> Keys => _dict.Keys;

        public ICollection<object> Values => _dict.Values;

        public int Count => _dict.Count;

        public bool IsReadOnly => false;

        public void Add(string key, object value)
        {
            _dict.Add(key, value);
        }

        public void Add(KeyValuePair<string, object> item)
        {
            _dict.Add(item.Key, item.Value);
        }

        public void Clear()
        {
            _dict.Clear();
        }

        public bool Contains(KeyValuePair<string, object> item)
        {
            return _dict.Contains(item);
        }

        public bool ContainsKey(string key)
        {
            return _dict.ContainsKey(key);
        }

        public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
        {
            _dict.CopyTo(array, arrayIndex);
        }

        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            return _dict.GetEnumerator();
        }

        public bool Remove(string key)
        {
            return _dict.Remove(key);
        }

        public bool Remove(KeyValuePair<string, object> item)
        {
            return _dict.Remove(item);
        }

        public bool TryGetValue(string key, out object value)
        {
            return _dict.TryGetValue(key, out value);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions