Поскольку этот поток немного разветвился в общем обсуждении текущих реализаций с плавающей запятой, я бы добавил, что есть проекты по исправлению их проблем.
Взгляните на https: // posithub.org/, который демонстрирует тип номера, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством бит. Если мое понимание верное, оно также фиксирует проблемы в вопросе. Весьма интересный проект, человек, стоящий за ним, является математиком Dr. Джон Густафсон . Все это с открытым исходным кодом, с множеством реализаций в C / C ++, Python, Julia и C # ( https://hastlayer.com/arithmetics ).
Ну, если вам просто нужно одно дополнительное свойство, один простой подход - разобрать ваш JSON на JObject
, используйте ToObject()
для заполнения вашего класса из JObject
, а затем используйте SelectToken()
, чтобы вытащить в дополнительном свойстве.
Итак, если ваш класс выглядел примерно так:
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public string Age { get; set; }
public string ProfilePicture { get; set; }
}
Вы могли бы сделать это:
string json = @"
{
""name"" : ""Joe Shmoe"",
""age"" : 26,
""picture"":
{
""id"": 123456,
""data"":
{
""type"": ""jpg"",
""url"": ""http://www.someplace.com/mypicture.jpg""
}
}
}";
JObject jo = JObject.Parse(json);
Person p = jo.ToObject<Person>();
p.ProfilePicture = (string)jo.SelectToken("picture.data.url");
Fiddle: https://dotnetfiddle.net/7gnJCK
Если вы предпочитаете более привлекательное решение, вы можете сделать пользовательский JsonConverter
, чтобы включить атрибут JsonProperty
как вы описали. Конвертер должен работать на уровне класса и использовать некоторое отражение в сочетании с вышеупомянутой техникой для заполнения всех свойств. Вот что это могло бы выглядеть в коде:
class JsonPathConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Чтобы продемонстрировать, предположим, что JSON теперь выглядит следующим образом:
{
"name": "Joe Shmoe",
"age": 26,
"picture": {
"id": 123456,
"data": {
"type": "jpg",
"url": "http://www.someplace.com/mypicture.jpg"
}
},
"favorites": {
"movie": {
"title": "The Godfather",
"starring": "Marlon Brando",
"year": 1972
},
"color": "purple"
}
}
... и вы заинтересованы в любимом фильме человека (название и год) и любимом цвете в дополнение к информации из ранее. Сначала вы отметите целевой класс атрибутом [JsonConverter]
, чтобы связать его с пользовательским конвертером, затем используйте атрибуты [JsonProperty]
для каждого свойства, указав желаемый путь свойства (с учетом регистра) в качестве имени. Целевые свойства также не должны быть примитивами: вы можете использовать дочерний класс, как я здесь, с Movie
(и обратите внимание, что нет требуемого класса Favorites
).
[JsonConverter(typeof(JsonPathConverter))]
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("picture.data.url")]
public string ProfilePicture { get; set; }
[JsonProperty("favorites.movie")]
public Movie FavoriteMovie { get; set; }
[JsonProperty("favorites.color")]
public string FavoriteColor { get; set; }
}
// Don't need to mark up these properties because they are covered by the
// property paths in the Person class
class Movie
{
public string Title { get; set; }
public int Year { get; set; }
}
Со всеми атрибутами на месте вы можете просто десериализовать как обычно, и он должен «просто работать»:
Person p = JsonConvert.DeserializeObject<Person>(json);
Fiddle: https://dotnetfiddle.net/Ljw32O
Если кому-то нужно использовать JsonPathConverter @BrianRogers также с опцией WriteJson
, вот решение (которое работает только для путей только с точками):
Удалите свойство CanWrite
, чтобы он снова становится true
.
Замените код WriteJson
следующим образом:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties ().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject ();
foreach (PropertyInfo prop in properties) {
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
var nesting=jsonPath.Split(new[] { '.' });
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++) {
if (i == nesting.Length - 1) {
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
} else {
if (lastLevel [nesting [i]] == null) {
lastLevel [nesting [i]] = new JObject ();
}
lastLevel = (JObject)lastLevel [nesting [i]];
}
}
}
serializer.Serialize (writer, main);
}
Как я уже говорил, это работает только для путей, содержащих точки. Учитывая это, вы должны добавить следующий код в ReadJson
, чтобы предотвратить другие случаи:
[...]
string jsonPath = (att != null ? att.PropertyName : prop.Name);
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) {
throw new InvalidOperationException("JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots."); //Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
[...]
Вместо выполнения
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
Вы должны выполнить
lastLevel[nesting[i]] = JValue.FromObject(jValue);
В противном случае у нас есть
Не удалось определить тип объекта JSON для типа ...
blockquote> Исключение
Отмеченный ответ не выполнен на 100%, так как он игнорирует любой IContractResolver, который может быть зарегистрирован, например CamelCasePropertyNamesContractResolver и т. д.
Также возврат false для can convert может предотвратить другие случаи пользователя, поэтому я изменил его на return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
Вот обновленная версия: https://dotnetfiddle.net/F8C8U8
Я также удалил необходимость установить JsonProperty
на свойство, как показано в ссылке.
Если по какой-либо причине ссылка выше умирает или взрывается, я также включаю следующий код:
public class JsonPathConverter : JsonConverter
{
/// <inheritdoc />
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$"))
{
throw new InvalidOperationException($"JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots but name was ${jsonPath}."); // Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject();
foreach (PropertyInfo prop in properties)
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
var nesting = jsonPath.Split('.');
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++)
{
if (i == nesting.Length - 1)
{
lastLevel[nesting[i]] = new JValue(prop.GetValue(value));
}
else
{
if (lastLevel[nesting[i]] == null)
{
lastLevel[nesting[i]] = new JObject();
}
lastLevel = (JObject)lastLevel[nesting[i]];
}
}
}
serializer.Serialize(writer, main);
}
}