using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
public class SingleOrArrayCollectionConverter<TItem> : JsonConverter<Collection<TItem>>
public SingleOrArrayCollectionConverter() : this(true) { }
public SingleOrArrayCollectionConverter(bool canWrite) => CanWrite = canWrite;
public bool CanWrite { get; }
public override bool CanConvert(Type typeToConvert) => typeof(Collection<TItem>).IsAssignableFrom(typeToConvert);
public override Collection<TItem> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
var list = typeToConvert == typeof(Collection<TItem>) ? new Collection<TItem>() : (Collection<TItem>)Activator.CreateInstance(typeToConvert);
switch (reader.TokenType)
case JsonTokenType.StartArray:
if (reader.TokenType == JsonTokenType.EndArray)
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
public override void Write(Utf8JsonWriter writer, Collection<TItem> value, JsonSerializerOptions options)
if (CanWrite && value.Count == 1)
JsonSerializer.Serialize(writer, value.First(), options);
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
public Collection<string> Values { get; set; }
public ObservableCollection<string> ObservableValues { get; set; }
public static void Test()
public static void TestSimple()
var options = new JsonSerializerOptions
Converters = { new SingleOrArrayCollectionConverter<string>() },
var collection = JsonSerializer.Deserialize<ObservableCollection<string>>(json, options);
Assert.IsTrue(collection.SequenceEqual(new [] { "hello" }));
public static void TestItem()
var options = new JsonSerializerOptions
Converters = { new SingleOrArrayCollectionConverter<string>() },
var item = JsonSerializer.Deserialize<Item>(json, options);
var json2 = JsonSerializer.Serialize(item, options);
AssertEqualsJson(json, json2);
static void AssertEqualsJson(string json1, string json2)
Assert.IsTrue(Newtonsoft.Json.Linq.JToken.DeepEquals(
Newtonsoft.Json.Linq.JToken.Parse(json1, new Newtonsoft.Json.Linq.JsonLoadSettings { CommentHandling = Newtonsoft.Json.Linq.CommentHandling.Ignore }),
Newtonsoft.Json.Linq.JToken.Parse(json2, new Newtonsoft.Json.Linq.JsonLoadSettings { CommentHandling = Newtonsoft.Json.Linq.CommentHandling.Ignore })
""ObservableValues"" : ""hello""
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];