Как десериализовать JSON, содержащий массив объектов с одним именем свойства и значением каждого, в модель c #?

У меня такая модель:

public class UserPtr
{
    public int my_var1 { get; set; }
    public int my_var2 { get; set; }
    public int my_var3 { get; set; }
    public int my_var4 { get; set; }
}

И некоторый ответ API JSON, который:

[ 
    {
        "name": "my_var1",
        "ptr": 1 // "Value_my_var1"
    },
    {
        "name": "my_var2",
        "ptr": 2 // "Value_my_var2"
    },
    {
        "name": "my_var3",
        "ptr": 3 // "Value_my_var3"
    },
    {
        "name": "my_var4",
        "ptr": 4 // "Value_my_var4"
    }
]

Я хочу установить my_var1 = Value_my_var1, my_var2 = Value_my_var2, my_var3 = Value_my_var3

Обычно я бы использовал:

JsonConvert.DeserializeObject<UserPtr>(strJson);

Но когда я это делаю, я получаю следующее исключение:

Newtonsoft.Json.JsonSerializationException: невозможно десериализовать текущий массив JSON (например, [1,2,3]) в тип UserPtr, потому что для правильной десериализации типу требуется объект JSON (например, {name: value}). Чтобы исправить эту ошибку, либо измените JSON на объект JSON (например, {name: value}), либо измените десериализованный тип на массив или тип, реализующий интерфейс коллекции (например, ICollection, IList), например List, который можно десериализовать из Массив JSON. JsonArrayAttribute также можно добавить к типу, чтобы заставить его десериализоваться из массива JSON.

Как я могу десериализовать этот массив объектов, содержащих имена и значения свойств, в мою модель?


person Chakrit    schedule 03.03.2021    source источник
comment
Кажется немного похожим на десериализацию JSON из результата поиска Sharepoint 2013 в список MyClass, в котором сериализуется объект c # как массив объектов, содержащих пары Имя свойства / Значение свойства.   -  person dbc    schedule 03.03.2021
comment
Вы не можете преобразовать строковые значения, такие как "Value_my_var1", в целые числа. Могу ли я предположить, что это на самом деле правильные целые числа, которые вы заменили при вводе вопроса?   -  person dbc    schedule 04.03.2021
comment
Вы должны опубликовать свой фактический JSON и реальную модель класса. То, что вы опубликовали, - это просто набор свойств без реального контейнера и, скорее всего, поддельный JSON.   -  person Jimi    schedule 04.03.2021
comment
Я сделал предположение, что реальные ptr значения объектов my_varX являются действительными целыми числами, и отредактировал ваш вопрос, чтобы отразить это. Если мое изменение было неправильным, отредактируйте свой вопрос еще раз, включив минимально воспроизводимый пример.   -  person dbc    schedule 04.03.2021


Ответы (1)


Вы хотите сериализовать свою модель как массив объектов, содержащих имена свойств и значения свойств, где имена и значения берутся из сериализации JSON по умолчанию для вашей модели. Вы можете сделать это с помощью настраиваемого универсального JsonConverter<T>, который переводится между значениями по умолчанию сериализация и сериализация массива.

По умолчанию ваша UserPtr модель должна быть сериализована следующим образом:

{
  "my_var1": 1,
  "my_var2": 2,
  "my_var3": 2,
  "my_var4": 4
}

Но вместо этого вы получаете массив объектов, содержащих отдельные пары имя / значение, как показано в вашем вопросе, где имена соответствуют именам свойств вашей модели. Вы хотите привязать этот массив к своей модели. Для этого вы можете создать универсальный конвертер, аналогичный конвертеру из Десериализовать JSON из результатов поиска Sharepoint 2013 в список MyClass следующим образом:

public class NamePtrPropertyArrayConverter<T> : JsonConverter<T> where T : class, new()
{
    struct NamePtrDTO
    {
        public string name;
        public object ptr;
    }
    
    public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
    {
        var obj = (JObject)JsonExtensions.DefaultFromObject(serializer, value);
        serializer.Serialize(writer, obj.Properties().Select(p => new NamePtrDTO { name = p.Name, ptr = p.Value }));
    }

    public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        var array = serializer.Deserialize<List<NamePtrDTO>>(reader);
        var obj = new JObject(array.Select(i => new JProperty(i.name, i.ptr)));
        existingValue = existingValue ?? (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        using (var subReader = obj.CreateReader())
            serializer.Populate(subReader, existingValue);
        return existingValue;
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }

    // DefaultFromObject() taken from this answer https://stackoverflow.com/a/29720068/3744182
    // By https://stackoverflow.com/users/3744182/dbc
    // To https://stackoverflow.com/questions/29719509/json-net-throws-stackoverflowexception-when-using-jsonconvert
    
    public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
    {
        if (value == null)
            return JValue.CreateNull();
        var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
        var root = JObject.FromObject(dto, serializer);
        return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
    }

    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        // If the parent is a JProperty, remove that instead of the token itself.
        var contained = node.Parent is JProperty ? node.Parent : node;
        contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (contained is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }

    interface IHasValue
    {
        object GetValue();
    }

    [JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
    class DefaultSerializationDTO<T> : IHasValue
    {
        public DefaultSerializationDTO(T value) => this.Value = value;
        public DefaultSerializationDTO() { }
        [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
        public T Value { get; set; }
        object IHasValue.GetValue() => Value;
    }       
}

public class NoConverter : JsonConverter
{
    // NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
    // By https://stackoverflow.com/users/3744182/dbc
    // To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
    public override bool CanConvert(Type objectType)  { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Затем выполните десериализацию, добавив конвертер в JsonSerializerSettings.Converters:

var settings = new JsonSerializerSettings
{
    Converters = { new NamePtrPropertyArrayConverter<UserPtr>() },
};
var model = JsonConvert.DeserializeObject<UserPtr>(strJson, settings);

Или примените преобразователь непосредственно к вашей модели следующим образом:

[JsonConverter(typeof(NamePtrPropertyArrayConverter<UserPtr>))]
public class UserPtr
{
    // Contents unchanged
}

Демо-скрипт здесь.

person dbc    schedule 03.03.2021