diff --git a/YamlDotNet.Test/Serialization/GenericTestReadOnlyDictionary.cs b/YamlDotNet.Test/Serialization/GenericTestReadOnlyDictionary.cs
new file mode 100644
index 00000000..84736e04
--- /dev/null
+++ b/YamlDotNet.Test/Serialization/GenericTestReadOnlyDictionary.cs
@@ -0,0 +1,85 @@
+// This file is part of YamlDotNet - A .NET library for YAML.
+// Copyright (c) Antoine Aubry and contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YamlDotNet.Test.Serialization
+{
+ ///
+ /// Test Dictionary that implements , but not
+ /// nor .
+ ///
+ public class GenericTestReadOnlyDictionary : IReadOnlyDictionary
+ {
+ private readonly Dictionary dictionary;
+
+ public GenericTestReadOnlyDictionary()
+ {
+ dictionary = new Dictionary();
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ dictionary.Add(key, value);
+ }
+
+ public TValue this[TKey key]
+ {
+ get { return dictionary[key]; }
+ }
+
+ public IEnumerable Keys
+ {
+ get { return dictionary.Keys; }
+ }
+
+ public IEnumerable Values
+ {
+ get { return dictionary.Values; }
+ }
+
+ public int Count
+ {
+ get { return dictionary.Count; }
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return dictionary.ContainsKey(key);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return dictionary.TryGetValue(key, out value);
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return dictionary.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return dictionary.GetEnumerator();
+ }
+ }
+}
diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs
index e0ff5c29..2ccabc00 100644
--- a/YamlDotNet.Test/Serialization/SerializationTests.cs
+++ b/YamlDotNet.Test/Serialization/SerializationTests.cs
@@ -1577,6 +1577,29 @@ public void SerializeGenericDictionaryPropertyAndDoNotApplyNamingConvention()
writer.ToString().Should().Contain("new_key_here: new_value");
}
+ [Fact]
+ public void SerializeGenericReadOnlyDictionary()
+ {
+ // A type that implements only IReadOnlyDictionary<,> must serialize as a
+ // mapping, the same as a regular dictionary, rather than as a sequence of
+ // key/value pairs. See https://github.com/aaubry/YamlDotNet/issues/606.
+ var readOnlyDictionary = new GenericTestReadOnlyDictionary();
+ readOnlyDictionary.Add("key1", "value1");
+ readOnlyDictionary.Add("key2", "value2");
+
+ var serializer = new SerializerBuilder().Build();
+
+ var actual = serializer.Serialize(readOnlyDictionary);
+ var expected = serializer.Serialize(
+ new Dictionary
+ {
+ ["key1"] = "value1",
+ ["key2"] = "value2",
+ });
+
+ actual.Should().Be(expected);
+ }
+
[Theory, MemberData(nameof(SpecialFloats))]
public void SpecialFloatsAreHandledCorrectly(FloatTestCase testCase)
{
diff --git a/YamlDotNet/Helpers/GenericReadOnlyDictionaryToNonGenericAdapter.cs b/YamlDotNet/Helpers/GenericReadOnlyDictionaryToNonGenericAdapter.cs
new file mode 100644
index 00000000..7f7099f9
--- /dev/null
+++ b/YamlDotNet/Helpers/GenericReadOnlyDictionaryToNonGenericAdapter.cs
@@ -0,0 +1,168 @@
+// This file is part of YamlDotNet - A .NET library for YAML.
+// Copyright (c) Antoine Aubry and contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace YamlDotNet.Helpers
+{
+ ///
+ /// Adapts an to
+ /// so that types which implement only the read-only interface are still traversed as mappings.
+ /// Only enumeration is supported, which is all that object graph traversal requires.
+ ///
+ internal sealed class GenericReadOnlyDictionaryToNonGenericAdapter : IDictionary
+ where TKey : notnull
+ {
+ private readonly IReadOnlyDictionary genericDictionary;
+
+ public GenericReadOnlyDictionaryToNonGenericAdapter(IReadOnlyDictionary genericDictionary)
+ {
+ this.genericDictionary = genericDictionary ?? throw new ArgumentNullException(nameof(genericDictionary));
+ }
+
+ public void Add(object key, object? value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Contains(object key)
+ {
+ throw new NotSupportedException();
+ }
+
+ public IDictionaryEnumerator GetEnumerator()
+ {
+ return new DictionaryEnumerator(genericDictionary.GetEnumerator());
+ }
+
+ public bool IsFixedSize
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public bool IsReadOnly
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public ICollection Keys
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public void Remove(object key)
+ {
+ throw new NotSupportedException();
+ }
+
+ public ICollection Values
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public object? this[object key]
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ public int Count
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public bool IsSynchronized
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public object SyncRoot
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ private class DictionaryEnumerator : IDictionaryEnumerator
+ {
+ private readonly IEnumerator> enumerator;
+
+ public DictionaryEnumerator(IEnumerator> enumerator)
+ {
+ this.enumerator = enumerator;
+ }
+
+ public DictionaryEntry Entry
+ {
+ get
+ {
+ return new DictionaryEntry(Key, Value);
+ }
+ }
+
+ public object Key
+ {
+ get { return enumerator.Current.Key!; }
+ }
+
+ public object? Value
+ {
+ get { return enumerator.Current.Value; }
+ }
+
+ public object Current
+ {
+ get { return Entry; }
+ }
+
+ public bool MoveNext()
+ {
+ return enumerator.MoveNext();
+ }
+
+ public void Reset()
+ {
+ enumerator.Reset();
+ }
+ }
+ }
+}
diff --git a/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs b/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs
index 3c363cee..fee3c1c7 100644
--- a/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs
+++ b/YamlDotNet/Serialization/ObjectFactories/ObjectFactoryBase.cs
@@ -63,6 +63,16 @@ public virtual bool GetDictionary(IObjectDescriptor descriptor, out IDictionary?
dictionary = adaptedDictionary as IDictionary;
return true;
}
+
+ var genericReadOnlyDictionaryType = descriptor.Type.GetImplementationOfOpenGenericInterface(typeof(IReadOnlyDictionary<,>));
+ if (genericReadOnlyDictionaryType != null)
+ {
+ genericArguments = genericReadOnlyDictionaryType.GetGenericArguments();
+ var adaptedDictionary = Activator.CreateInstance(typeof(GenericReadOnlyDictionaryToNonGenericAdapter<,>).MakeGenericType(genericArguments), descriptor.Value)!;
+ dictionary = adaptedDictionary as IDictionary;
+ return true;
+ }
+
genericArguments = null;
dictionary = null;
return false;