Если проблема в привязки определённых полей объекта к определённым полям json:
В случае, если вы хотите вместо простого имени поля указать путь до этого самого поля, то это решение для вас:
Решение для System.Text.Json:
Я долго думал и всё же сделал довольно простое решение для стандартной библиотеки:
public class PathJsonConverter<T> : JsonConverter<T> where T : new()
{
// Этот метод упрощает создание настроек
public static T? Deserialize(string json, JsonNamingPolicy? namingPolicy = null)
{
var options = new JsonSerializerOptions();
options.PropertyNamingPolicy = namingPolicy;
options.Converters.Add(new PathJsonConverter<T>());
return JsonSerializer.Deserialize<T>(json, options);
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var jsonObject = JsonNode.Parse(ref reader);
T? result = new T();
foreach (var property in typeToConvert.GetProperties())
{
JsonNode? currentNode = jsonObject;
var attr = property.GetCustomAttribute<JsonPropertyNameAttribute>();
if (attr == null)
currentNode = currentNode?[options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name];
else
foreach (var path in attr.Name.Split('.'))
currentNode = currentNode?[path];
if (currentNode != null)
property.SetValue(result, getObject(currentNode));
}
return result;
}
object? getObject(JsonNode node) => node.GetValue<JsonElement>().ValueKind switch
{
JsonValueKind.Null => null,
JsonValueKind.Undefined => null,
JsonValueKind.String => node.GetValue<string>(),
JsonValueKind.Array => node.GetValue<JsonArray>(),
JsonValueKind.Object => node.GetValue<JsonObject>(),
JsonValueKind.Number => node.AsValue().TryGetValue(out int val) ? (object)val : (node.AsValue().TryGetValue(out float fval) ? (object)fval : (object)node.GetValue<double>()),
JsonValueKind.False => false,
JsonValueKind.True => true,
_ => null
};
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
throw new NotImplementedException();
}
Данный способ позволяет указывать json путь в JsonPropertyName, а так же позволяет использовать имя поля когда путь не указан и поддерживает кастомный PropertyNamingPolicy
(из решения ниже)
Использование:
class Model
{
[JsonPropertyName("values.val")]
public int ValuesVal { get; set; }
[JsonPropertyName("single")]
public int Value { get; set; }
public string? OtherValue { get; set; }
}
string json = @"{""values"":{""val"":4}, ""single"": 2, ""other_value"": ""Here is other value""}";
// SnakeCaseNamingPolicy взят из решения
// проблемы с политикой наименования ниже
// Данный параметр не обязателен, и нужен только
// для преобразования из "OtherValue" в "other_value"
Model? model = PathJsonConverter<Model>.Deserialize(json, new SnakeCaseNamingPolicy());
Console.WriteLine($"{model?.Value}, {model?.ValuesVal}, {model?.OtherValue}");
Решение для Newtonsoft.Json:
Я рекомендую вам использовать Newtonsoft.Json, так как он представляет очень мощный функционал для работы с json и лучше кастомизируется.
class JsonPathConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JObject jsonObj = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType) ?? throw new JsonException($"Can't create instance of {objectType}");
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute? att = prop.GetCustomAttributes(true).OfType<JsonPropertyAttribute>().FirstOrDefault();
string jsonPath = att?.PropertyName ?? prop.Name;
JToken? token = jsonObj.SelectToken(jsonPath);
if ((token?.Type ?? JTokenType.Null) != JTokenType.Null)
{
object? value = token?.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType) => false;
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => throw new NotImplementedException();
}
Использование:
[JsonConverter(typeof(JsonPathConverter))]
class TestClass
{
[JsonProperty("values.some_value")]
public int SomeValue { get; set; }
}
TestClass testClass = JsonConvert.DeserializeObject<TestClass>(@"{""values"": {""some_value"": 5}}");
Console.WriteLine(testClass.SomeValue)
Если проблема в политики наименования:
Если же проблема в соответствии способов наименования (типо ObjectName
и object_name
), то вам сюда
Решение для System.Net.Json:
public class SnakeCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) =>
string.Concat(name.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
}
Использование:
TestObject testObject = JsonSerializer.Deserialize<TestObject>(
"{\"int_value\": 5}",
new JsonSerializerOptions()
{
PropertyNamingPolicy = new SnakeCaseNamingPolicy()
}
);
Console.WriteLine(testObject.IntValue);
Решение для Newtonsoft.Json:
Решение для данной задачи уже была встроена в библиотеку:
TestObject testObject = JsonConvert.DeserializeObject<TestObject>(
"{\"int_value\": 5}",
new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
{ NamingStrategy = new SnakeCaseNamingStrategy() }
}
);
Console.WriteLine(testObject.IntValue);