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 string Title { get; set; }
public string Head { get; set; }
public string Link { get; set; }
public class JsonPopulator
public void PopulateObject(object obj, string jsonString, JsonSerializerOptions options = null) => PopulateObject(obj, jsonString != null ? Encoding.UTF8.GetBytes(jsonString) : null, options);
public virtual void PopulateObject(object obj, ReadOnlySpan<byte> jsonData, JsonSerializerOptions options = null)
options ??= new JsonSerializerOptions();
var state = new JsonReaderState(new JsonReaderOptions { AllowTrailingCommas = options.AllowTrailingCommas, CommentHandling = options.ReadCommentHandling, MaxDepth = options.MaxDepth });
var reader = new Utf8JsonReader(jsonData, isFinalBlock: true, state);
new Worker(this, reader, obj, options);
protected virtual PropertyInfo GetProperty(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
throw new ArgumentNullException(nameof(obj));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var prop = obj.GetType().GetProperty(propertyName);
protected virtual bool SetPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, object obj, string propertyName)
throw new ArgumentNullException(nameof(obj));
if (propertyName == null)
throw new ArgumentNullException(nameof(propertyName));
var prop = GetProperty(ref reader, options, obj, propertyName);
if (!TryReadPropertyValue(ref reader, options, prop.PropertyType, out var value))
prop.SetValue(obj, value);
protected virtual bool TryReadPropertyValue(ref Utf8JsonReader reader, JsonSerializerOptions options, Type propertyType, out object value)
if (propertyType == null)
throw new ArgumentNullException(nameof(reader));
if (reader.TokenType == JsonTokenType.Null)
return !propertyType.IsValueType || Nullable.GetUnderlyingType(propertyType) != null;
if (propertyType == typeof(object)) { value = ReadValue(ref reader); return true; }
if (propertyType == typeof(string)) { value = JsonSerializer.Deserialize<JsonElement>(ref reader, options).GetRawText(); return true; }
if (propertyType == typeof(int) && reader.TryGetInt32(out var i32)) { value = i32; return true; }
if (propertyType == typeof(long) && reader.TryGetInt64(out var i64)) { value = i64; return true; }
if (propertyType == typeof(DateTime) && reader.TryGetDateTime(out var dt)) { value = dt; return true; }
if (propertyType == typeof(DateTimeOffset) && reader.TryGetDateTimeOffset(out var dto)) { value = dto; return true; }
if (propertyType == typeof(Guid) && reader.TryGetGuid(out var guid)) { value = guid; return true; }
if (propertyType == typeof(decimal) && reader.TryGetDecimal(out var dec)) { value = dec; return true; }
if (propertyType == typeof(double) && reader.TryGetDouble(out var dbl)) { value = dbl; return true; }
if (propertyType == typeof(float) && reader.TryGetSingle(out var sgl)) { value = sgl; return true; }
if (propertyType == typeof(uint) && reader.TryGetUInt32(out var ui32)) { value = ui32; return true; }
if (propertyType == typeof(ulong) && reader.TryGetUInt64(out var ui64)) { value = ui64; return true; }
if (propertyType == typeof(byte[]) && reader.TryGetBytesFromBase64(out var bytes)) { value = bytes; return true; }
if (propertyType == typeof(bool))
if (reader.TokenType == JsonTokenType.False || reader.TokenType == JsonTokenType.True)
value = reader.GetBoolean();
return TryConvertValue(ref reader, propertyType, out value);
protected virtual object ReadValue(ref Utf8JsonReader reader)
switch (reader.TokenType)
case JsonTokenType.False: return false;
case JsonTokenType.True: return true;
case JsonTokenType.Null: return null;
case JsonTokenType.String: return reader.GetString();
case JsonTokenType.Number:
if (reader.TryGetInt32(out var i32))
if (reader.TryGetInt64(out var i64))
if (reader.TryGetUInt64(out var ui64))
if (reader.TryGetSingle(out var sgl))
if (reader.TryGetDouble(out var dbl))
if (reader.TryGetDecimal(out var dec))
throw new NotSupportedException();
protected virtual bool TryConvertValue(ref Utf8JsonReader reader, Type propertyType, out object value)
if (propertyType == null)
throw new ArgumentNullException(nameof(reader));
if (propertyType == typeof(bool))
if (reader.TryGetInt64(out var i64))
protected virtual object CreateInstance(ref Utf8JsonReader reader, Type propertyType)
if (propertyType.GetConstructor(Type.EmptyTypes) == null)
return Activator.CreateInstance(propertyType);
private readonly Stack<WorkerProperty> _properties = new Stack<WorkerProperty>();
private readonly Stack<object> _objects = new Stack<object>();
public Worker(JsonPopulator populator, Utf8JsonReader reader, object obj, JsonSerializerOptions options)
switch (reader.TokenType)
case JsonTokenType.PropertyName:
prop = new WorkerProperty();
prop.PropertyName = Encoding.UTF8.GetString(reader.ValueSpan);
case JsonTokenType.StartObject:
case JsonTokenType.StartArray:
if (_properties.Count > 0)
var parent = _objects.Peek();
pi = populator.GetProperty(ref reader, options, parent, _properties.Peek().PropertyName);
child = pi.GetValue(parent);
if (child == null && pi.CanWrite)
if (reader.TokenType == JsonTokenType.StartArray)
if (!typeof(IList).IsAssignableFrom(pi.PropertyType))
if (reader.TokenType == JsonTokenType.StartArray && pi.PropertyType.IsArray)
child = Activator.CreateInstance(typeof(List<>).MakeGenericType(pi.PropertyType.GetElementType()));
child = populator.CreateInstance(ref reader, pi.PropertyType);
pi.SetValue(parent, child);
if (reader.TokenType == JsonTokenType.StartObject)
peek = _properties.Peek();
peek.IsArray = pi.PropertyType.IsArray;
peek.List = (IList)child;
peek.ListPropertyType = GetListElementType(child.GetType());
peek.ArrayPropertyInfo = pi;
case JsonTokenType.EndObject:
if (_properties.Count > 0)
case JsonTokenType.EndArray:
if (_properties.Count > 0)
prop = _properties.Pop();
var array = Array.CreateInstance(GetListElementType(prop.ArrayPropertyInfo.PropertyType), prop.List.Count);
prop.List.CopyTo(array, 0);
prop.ArrayPropertyInfo.SetValue(_objects.Peek(), array);
case JsonTokenType.False:
case JsonTokenType.Number:
case JsonTokenType.String:
peek = _properties.Peek();
if (populator.TryReadPropertyValue(ref reader, options, peek.ListPropertyType, out var item))
prop = _properties.Pop();
var current = _objects.Peek();
populator.SetPropertyValue(ref reader, options, current, prop.PropertyName);
private static Type GetListElementType(Type type)
return type.GetElementType();
foreach (Type iface in type.GetInterfaces())
if (!iface.IsGenericType) continue;
if (iface.GetGenericTypeDefinition() == typeof(IDictionary<,>)) return iface.GetGenericArguments()[1];
if (iface.GetGenericTypeDefinition() == typeof(IList<>)) return iface.GetGenericArguments()[0];
if (iface.GetGenericTypeDefinition() == typeof(ICollection<>)) return iface.GetGenericArguments()[0];
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) return iface.GetGenericArguments()[0];
private class WorkerProperty
public string PropertyName;
public Type ListPropertyType;
public PropertyInfo ArrayPropertyInfo;
public override string ToString() => PropertyName;
public static void Test()
TestStandalonePopulate();
static void AssertEqual(MyClass class1, MyClass class2)
Assert.IsTrue(class1 != null && class2 != null);
Assert.AreEqual(class1.Title, class2.Title);
Assert.AreEqual(class1.Head, class2.Head);
Assert.AreEqual(class1.Link, class2.Link);
static void TestStandalonePopulate()
var myClass = new MyClass
var json = JsonSerializer.Serialize(myClass);
var class2 = new MyClass();
new JsonPopulator().PopulateObject(class2, json, null);
AssertEqual(myClass, class2);
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.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];