Итак, если предположить, что Core 3 не поддерживает это из коробки, давайте попробуем обойти это.Итак, в чем наша проблема?
Нам нужен метод, который перезаписывает некоторые свойства существующего объекта таковыми из строки json.Таким образом, наш метод будет иметь подпись:
void PopulateObject<T>(T target, string jsonSource) where T : class
Нам не нужен какой-либо пользовательский синтаксический анализ, поскольку он громоздок, поэтому мы попробуем очевидный подход - десериализовать jsonSource
и скопировать свойства результата внаш объект.Мы не можем, однако, просто пойти
T updateObject = JsonSerializer.Parse<T>(jsonSource);
CopyUpdatedProperties(target, updateObject);
Это потому, что для типа
class Example
{
int Id { get; set; }
int Value { get; set; }
}
и JSON
{
"Id": 42
}
мы получим updateObject.Value == 0
.Теперь мы не знаем, является ли 0
новым обновленным значением или оно просто не было обновлено, поэтому нам нужно точно знать, какие свойства jsonSource
содержит.
К счастью, System.Text.Json
APIпозволяет нам исследовать структуру проанализированного JSON.
var json = JsonDocument.Parse(jsonSource).RootElement;
Теперь мы можем перечислить все свойства и скопировать их.
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
Мы скопируем значение, используя отражение:
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
var parsedValue = JsonSerializer.Parse(updatedProperty.Value, propertyType);
propertyInfo.SetValue(target, parsedValue);
}
Здесь мы видим, что мы делаем обновление shallow .Если объект содержит в качестве своего свойства другой сложный объект, он будет скопирован и перезаписан целиком, а не обновлен.Если вам требуются обновления deep , этот метод необходимо изменить, чтобы извлечь текущее значение свойства, а затем рекурсивно вызвать PopulateObject
, если тип свойства является ссылочным типом (который также потребует принятия * 1036).* как параметр в PopulateObject
).
Объединив все это, мы получим:
void PopulateObject<T>(T target, string jsonSource) where T : class
{
var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
}
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
var parsedValue = JsonSerializer.Parse(updatedProperty.Value, propertyType);
propertyInfo.SetValue(target, parsedValue);
}
Насколько это надежно?Что ж, это определенно не будет делать ничего разумного для массива JSON, но я не уверен, как можно ожидать, что метод PopulateObject
будет работать с массивом для начала.Я не знаю, как она сравнивается по производительности с версией Json.Net
, вам придется проверить это самостоятельно.Он также автоматически игнорирует свойства, которые не относятся к целевому типу.Я думал, что это был самый разумный подход, но вы могли бы подумать иначе, в этом случае свойство null-check должно быть заменено на исключение.
EDIT:
Я пошел дальше и реализовал глубокую копию:
void PopulateObject<T>(T target, string jsonSource) where T : class =>
PopulateObject(target, jsonSource, typeof(T));
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class =>
OverwriteProperty(target, updatedProperty, typeof(T));
void PopulateObject(object target, string jsonSource, Type type)
{
var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property, type);
}
}
void OverwriteProperty(object target, JsonProperty updatedProperty, Type type)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
object parsedValue;
if (propertyType.IsValueType)
{
parsedValue = JsonSerializer.Parse(updatedProperty.Value, propertyType);
}
else
{
parsedValue = propertyInfo.GetValue(target);
PopulateObject(parsedValue, updatedProperty.Value, propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
Чтобы сделать это более надежным, вам потребуется либо отдельный метод PopulateObjectDeep
, либо пропуск PopulateObjectOptions
, либо что-то похожее с глубоким /мелкий флаг.
РЕДАКТИРОВАТЬ 2:
Точка глубокого копирования такова, что если у нас есть объект
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 32
},
"Value": 128
}
и заполнить егос
{
"Child":
{
"Value": 64
}
}
мы получили бы
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 64
},
"Value": 128
}
В случае мелкой копии мы получили бы Id = 0
в скопированном дочернем элементе.