diff --git a/docs/standard/serialization/system-text-json-converters-how-to.md b/docs/standard/serialization/system-text-json-converters-how-to.md index 37d42e15b80f9..4b2abdbbb17be 100644 --- a/docs/standard/serialization/system-text-json-converters-how-to.md +++ b/docs/standard/serialization/system-text-json-converters-how-to.md @@ -24,6 +24,7 @@ You can also write custom converters to customize or extend `System.Text.Json` w * [Deserialize inferred types to object properties](#deserialize-inferred-types-to-object-properties). * [Support Dictionary with non-string key](#support-dictionary-with-non-string-key). * [Support polymorphic deserialization](#support-polymorphic-deserialization). +* [Support round-trip for Stack\](#support-round-trip-for-stackt). ## Custom converter patterns @@ -172,6 +173,7 @@ The following sections provide converter samples that address some common scenar * [Deserialize inferred types to object properties](#deserialize-inferred-types-to-object-properties) * [Support Dictionary with non-string key](#support-dictionary-with-non-string-key) * [Support polymorphic deserialization](#support-polymorphic-deserialization) +* [Support round-trip for Stack\](#support-round-trip-for-stackt). ### Deserialize inferred types to object properties @@ -279,6 +281,26 @@ The converter can deserialize JSON that was created by using the same converter The converter code in the preceding example reads and writes each property manually. An alternative is to call `Deserialize` or `Serialize` to do some of the work. For an example, see [this StackOverflow post](https://stackoverflow.com/a/59744873/12509023). +### Support round-trip for Stack\ + +If you deserialize a JSON string into a object and then serialize that object, the contents of the stack are in reverse order. This behavior applies to the following types and interface, and user-defined types that derive from them: + +* +* +* +* +* + +To support serialization and deserialization that retains the original order in the stack, a custom converter is required. + +The following code shows a custom converter that enables round-tripping to and from `Stack` objects: + +[!code-csharp[](~/samples/snippets/core/system-text-json/csharp/JsonConverterFactoryForStackOfT.cs)] + +The following code registers the converter: + +[!code-csharp[](~/samples/snippets/core/system-text-json/csharp/RoundtripStackOfT.cs?name=SnippetRegister)] + ## Other custom converter samples The [Migrate from Newtonsoft.Json to System.Text.Json](system-text-json-migrate-from-newtonsoft-how-to.md) article contains additional samples of custom converters. diff --git a/samples/snippets/core/system-text-json/csharp/JsonConverterFactoryForStackOfT.cs b/samples/snippets/core/system-text-json/csharp/JsonConverterFactoryForStackOfT.cs new file mode 100644 index 0000000000000..3811fe60ee070 --- /dev/null +++ b/samples/snippets/core/system-text-json/csharp/JsonConverterFactoryForStackOfT.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SystemTextJsonSamples +{ + public class JsonConverterFactoryForStackOfT : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType && + typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>); + } + + public override JsonConverter CreateConverter( + Type typeToConvert, JsonSerializerOptions options) + { + Debug.Assert(typeToConvert.IsGenericType && + typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>)); + + Type elementType = typeToConvert.GetGenericArguments()[0]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(JsonConverterForStackOfT<>) + .MakeGenericType(new Type[] { elementType }), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: null, + culture: null)!; + + return converter; + } + } + + public class JsonConverterForStackOfT : JsonConverter> + { + public override Stack Read( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray || !reader.Read()) + { + throw new JsonException(); + } + + var elements = new Stack(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + elements.Push(JsonSerializer.Deserialize(ref reader, options)); + + if (!reader.Read()) + { + throw new JsonException(); + } + } + + return elements; + } + + public override void Write( + Utf8JsonWriter writer, Stack value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + var reversed = new Stack(value); + + foreach (T item in reversed) + { + JsonSerializer.Serialize(writer, item, options); + } + + writer.WriteEndArray(); + } + } +} diff --git a/samples/snippets/core/system-text-json/csharp/Program.cs b/samples/snippets/core/system-text-json/csharp/Program.cs index 557a78015bce5..a4bf0e9d7ebd1 100644 --- a/samples/snippets/core/system-text-json/csharp/Program.cs +++ b/samples/snippets/core/system-text-json/csharp/Program.cs @@ -36,6 +36,9 @@ static async Task Main(string[] args) Console.WriteLine("\n============================= Roundtrip enum as string\n"); RoundtripEnumAsString.Run(); + Console.WriteLine("\n============================= Roundtrip Stack\n"); + RoundtripStackOfT.Run(); + Console.WriteLine("\n============================= Serialize polymorphic\n"); SerializePolymorphic.Run(); diff --git a/samples/snippets/core/system-text-json/csharp/RoundtripStackOfT.cs b/samples/snippets/core/system-text-json/csharp/RoundtripStackOfT.cs new file mode 100644 index 0000000000000..123085d0a2a55 --- /dev/null +++ b/samples/snippets/core/system-text-json/csharp/RoundtripStackOfT.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text.Json; + +namespace SystemTextJsonSamples +{ + public class RoundtripStackOfT + { + public static void Run() + { + Console.WriteLine("Deserialize JSON string [1, 2, 3], then serialize it back to JSON."); + Stack stack = JsonSerializer.Deserialize>("[1, 2, 3]"); + string serialized = JsonSerializer.Serialize(stack); + Console.WriteLine($"Result is in reverse order: {serialized}"); + + Console.WriteLine("Deserialize JSON string [1, 2, 3] with custom converter, then serialize it back to JSON."); + // + var options = new JsonSerializerOptions + { + Converters = { new JsonConverterFactoryForStackOfT() }, + }; + // + stack = JsonSerializer.Deserialize>("[1, 2, 3]", options); + serialized = JsonSerializer.Serialize(stack, options); + Console.WriteLine($"Result is in same order: {serialized}"); + } + } +}