/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #region Utf8Json License https://github.com/neuecc/Utf8Json/blob/master/LICENSE // MIT License // // Copyright (c) 2017 Yoshifumi Kawai // // 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. #endregion using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; namespace OpenSearch.Net.Utf8Json.Formatters { // unfortunately, can't use IDictionary because supports IReadOnlyDictionary. internal abstract class DictionaryFormatterBase : IJsonFormatter where TDictionary : class, IEnumerable> where TEnumerator : IEnumerator> { protected bool SkipValue(TValue value) => false; public void Serialize(ref JsonWriter writer, TDictionary value, IJsonFormatterResolver formatterResolver) { if (value == null) writer.WriteNull(); else { var keyFormatter = formatterResolver.GetFormatterWithVerify() as IObjectPropertyNameFormatter; var valueFormatter = formatterResolver.GetFormatterWithVerify(); writer.WriteBeginObject(); var e = GetSourceEnumerator(value); try { var written = 0; if (keyFormatter != null) { if (e.MoveNext()) { var item = e.Current; if (!SkipValue(item.Value)) { keyFormatter.SerializeToPropertyName(ref writer, item.Key, formatterResolver); writer.WriteNameSeparator(); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); written++; } } else goto END; while (e.MoveNext()) { var item = e.Current; if (!SkipValue(item.Value)) { if (written > 0) writer.WriteValueSeparator(); keyFormatter.SerializeToPropertyName(ref writer, item.Key, formatterResolver); writer.WriteNameSeparator(); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); written++; } } } else { if (e.MoveNext()) { var item = e.Current; if (!SkipValue(item.Value)) { written++; writer.WriteString(item.Key.ToString()); writer.WriteNameSeparator(); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); } } else goto END; while (e.MoveNext()) { var item = e.Current; if (!SkipValue(item.Value)) { if (written > 0) writer.WriteValueSeparator(); writer.WriteString(item.Key.ToString()); writer.WriteNameSeparator(); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); written++; } } } } finally { e.Dispose(); } END: writer.WriteEndObject(); } } public TDictionary Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { if (reader.ReadIsNull()) return null; var keyFormatter = formatterResolver.GetFormatterWithVerify() as IObjectPropertyNameFormatter; if (keyFormatter == null) throw new InvalidOperationException(typeof(TKey) + " does not support dictionary key deserialize."); var valueFormatter = formatterResolver.GetFormatterWithVerify(); reader.ReadIsBeginObjectWithVerify(); var dict = Create(); var i = 0; while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref i)) { var key = keyFormatter.DeserializeFromPropertyName(ref reader, formatterResolver); reader.ReadIsNameSeparatorWithVerify(); var value = valueFormatter.Deserialize(ref reader, formatterResolver); Add(ref dict, i - 1, key, value); } return Complete(ref dict); } // abstraction for serialize // Some collections can use struct iterator, this is optimization path protected abstract TEnumerator GetSourceEnumerator(TDictionary source); // abstraction for deserialize protected abstract TIntermediate Create(); protected abstract void Add(ref TIntermediate collection, int index, TKey key, TValue value); protected abstract TDictionary Complete(ref TIntermediate intermediateCollection); } internal abstract class DictionaryFormatterBase : DictionaryFormatterBase>, TDictionary> where TDictionary : class, IEnumerable> { protected override IEnumerator> GetSourceEnumerator(TDictionary source) => source.GetEnumerator(); } internal abstract class DictionaryFormatterBase : DictionaryFormatterBase where TDictionary : class, IDictionary { protected override TDictionary Complete(ref TDictionary intermediateCollection) => intermediateCollection; } internal sealed class DictionaryFormatter : DictionaryFormatterBase, Dictionary.Enumerator, Dictionary> { protected override void Add(ref Dictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override Dictionary Complete(ref Dictionary intermediateCollection) => intermediateCollection; protected override Dictionary Create() => new Dictionary(); protected override Dictionary.Enumerator GetSourceEnumerator(Dictionary source) => source.GetEnumerator(); } internal sealed class GenericDictionaryFormatter : DictionaryFormatterBase where TDictionary : class, IDictionary, new() { protected override void Add(ref TDictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override TDictionary Create() => new TDictionary(); } internal sealed class InterfaceDictionaryFormatter : DictionaryFormatterBase, IDictionary> { protected override void Add(ref Dictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override Dictionary Create() => new Dictionary(); protected override IDictionary Complete(ref Dictionary intermediateCollection) => intermediateCollection; } internal sealed class SortedListFormatter : DictionaryFormatterBase> { protected override void Add(ref SortedList collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override SortedList Create() => new SortedList(); } internal sealed class SortedDictionaryFormatter : DictionaryFormatterBase, SortedDictionary.Enumerator, SortedDictionary> { protected override void Add(ref SortedDictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override SortedDictionary Complete(ref SortedDictionary intermediateCollection) => intermediateCollection; protected override SortedDictionary Create() => new SortedDictionary(); protected override SortedDictionary.Enumerator GetSourceEnumerator(SortedDictionary source) => source.GetEnumerator(); } internal sealed class ReadOnlyDictionaryFormatter : DictionaryFormatterBase, ReadOnlyDictionary> { protected override void Add(ref Dictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override ReadOnlyDictionary Complete(ref Dictionary intermediateCollection) => new ReadOnlyDictionary(intermediateCollection); protected override Dictionary Create() => new Dictionary(); } internal sealed class InterfaceReadOnlyDictionaryFormatter : DictionaryFormatterBase, IReadOnlyDictionary> { protected override void Add(ref Dictionary collection, int index, TKey key, TValue value) => collection.Add(key, value); protected override IReadOnlyDictionary Complete(ref Dictionary intermediateCollection) => intermediateCollection; protected override Dictionary Create() => new Dictionary(); } internal sealed class ConcurrentDictionaryFormatter : DictionaryFormatterBase> { protected override void Add(ref ConcurrentDictionary collection, int index, TKey key, TValue value) => collection.TryAdd(key, value); protected override ConcurrentDictionary Create() => // concurrent dictionary can't access defaultConcurrecyLevel so does not use count overload. new ConcurrentDictionary(); } internal sealed class NonGenericDictionaryFormatter : IJsonFormatter where T : class, IDictionary, new() { public void Serialize(ref JsonWriter writer, T value, IJsonFormatterResolver formatterResolver) { if (value == null) writer.WriteNull(); else { var valueFormatter = formatterResolver.GetFormatterWithVerify(); writer.WriteBeginObject(); var e = value.GetEnumerator(); try { if (e.MoveNext()) { var item = (DictionaryEntry)e.Current; writer.WritePropertyName(item.Key.ToString()); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); } else { goto END; } while (e.MoveNext()) { writer.WriteValueSeparator(); var item = (DictionaryEntry)e.Current; writer.WritePropertyName(item.Key.ToString()); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); } } finally { if (e is IDisposable disp) disp.Dispose(); } END: writer.WriteEndObject(); } } public T Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { if (reader.ReadIsNull()) return default; var valueFormatter = formatterResolver.GetFormatterWithVerify(); reader.ReadIsBeginObjectWithVerify(); var dict = new T(); var i = 0; while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref i)) { var key = reader.ReadPropertyName(); var value = valueFormatter.Deserialize(ref reader, formatterResolver); dict.Add(key, value); } return dict; } } internal sealed class NonGenericInterfaceDictionaryFormatter : IJsonFormatter { public static readonly IJsonFormatter Default = new NonGenericInterfaceDictionaryFormatter(); public void Serialize(ref JsonWriter writer, IDictionary value, IJsonFormatterResolver formatterResolver) { if (value == null) writer.WriteNull(); else { var valueFormatter = formatterResolver.GetFormatterWithVerify(); writer.WriteBeginObject(); var e = value.GetEnumerator(); try { if (e.MoveNext()) { var item = (DictionaryEntry)e.Current; writer.WritePropertyName(item.Key.ToString()); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); } else goto END; while (e.MoveNext()) { writer.WriteValueSeparator(); var item = (DictionaryEntry)e.Current; writer.WritePropertyName(item.Key.ToString()); valueFormatter.Serialize(ref writer, item.Value, formatterResolver); } } finally { if (e is IDisposable disp) disp.Dispose(); } END: writer.WriteEndObject(); } } public IDictionary Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { if (reader.ReadIsNull()) return null; var valueFormatter = formatterResolver.GetFormatterWithVerify(); reader.ReadIsBeginObjectWithVerify(); var dict = new Dictionary(); var i = 0; while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref i)) { var key = reader.ReadPropertyName(); var value = valueFormatter.Deserialize(ref reader, formatterResolver); dict.Add(key, value); } return dict; } } }