Десериализовать Json, используя C# с переменным содержимым

Я перепостил этот вопрос, потому что кто-то пометил его как дубликат, но это не так, поэтому я подробно рассказал, чтобы избежать путаницы.

Я создаю API в C#, который мой WebApp может получить, чтобы получить некоторые данные. В моем веб-приложении у меня есть представление со списком (скажем, продуктов). На данный момент у меня есть маршрут, который возвращает все мои продукты, хранящиеся в базе данных.

Теперь я хотел бы добавить возможность веб-приложения создавать сложные фильтры. Я хотел бы иметь возможность в WebApp или с Postman для примера, чтобы достичь моего маршрута (скажем) "/api/product" с URlParam с именем "filters", который будет содержать все фильтры, которые веб-приложение должно применить.

Теперь вернемся к моему WebApp (сборка с использованием angular). У меня есть сервис, который может создавать как дерево фильтров (см. Ниже архитектуру класса). (И да, эта архитектура может быть улучшена)

Идея заключается в том, что я могу создать фильтр следующим образом: если я хочу получить все товары, в названиях которых есть "foo" и цена которых превышает 15$:

let filter = new AndNode();
filter.Add(new LikeNode("name", "foo"));
filter.Add(new GTNode("price", 15));
filter.generateJson();

Приведет к этому Json:

{
    "$and": [
        { 
            "$like": { "key": "name", "value": "foo" }
        },
        {
            "$gt": { "key": "price", "value": 15 }
        }
    ]
}  

Теперь у этого Json есть 1 требование: он никогда не должен содержать более одного свойства на первом уровне. Поскольку это должно использоваться для генерации фильтров SQL, имеет смысл, что json должен содержать только один узел на своем первом уровне: узел EQ, LIKE, GT или LT, или если у нас более одного фильтра и AND / ИЛИ узлы, которые определяют логическое "соединение" между каждым фильтром следующего уровня.

Так что имеет смысл, что:

{
    "$like": { "key": "name", "value": "foo" }
}

Действителен и может привести к следующему выражению SQL:

SELECT * FROM products WHERE name LIKE "%foo%";

Также:

{
    "$and": [
        { 
            "$like": { "key": "name", "value": "foo" }
        },
        {
            "$gt": { "key": "price", "value": 15 }
        }
    ]
}  

Действителен и может привести к следующему выражению SQL:

SELECT * FROM products WHERE name LIKE "%foo%" AND price > 15;

Но:

{
    "$like": { "key": "name", "value": "foo" },
    "$eq": { "key": "price", "value": 5 },
}

Недопустимо, потому что это приведет к следующему SQL-выражению:

SELECT * FROM products WHERE name LIKE "%foo%" price > 15

Ведьма недопустима из-за пропущенного ключевого слова AND или OR.

Что я хотел бы сделать: в прекрасном мире я хотел бы иметь возможность сериализовать этот json в моем веб-приложении, преобразовать его в допустимую строку параметров URL и отправить его в API (C#), десериализовать этот JSON, чтобы получить та же структура объекта, что и в WebApp. Для нашего примера с ценой и именем, я хотел бы иметь экземпляр класса AndNode с членом "child", который содержит 2 объекта: 1 экземпляр класса LikeNode и 1 экземпляр класса GTNode.

Эта реализация может быть спорным, но это то, что нам нужно сейчас.

На данный момент у меня есть:

public class EQNode
{
    [JsonProperty("key")]
    public string key { get; set; }

    [JsonProperty("value")]
    public int value { get; set; }
}

public class LikeNode
{
    [JsonProperty("key")]
    public string key { get; set; }

    [JsonProperty("value")]
    public string value { get; set; }
}

public class GTNode
{
    [JsonProperty("key")]
    public string key { get; set; }

    [JsonProperty("value")]
    public int value { get; set; }
}

public class LTNode
{
    [JsonProperty("key")]
    public string key { get; set; }

    [JsonProperty("value")]
    public int value { get; set; }
}

public class OrNode
{
    [JsonProperty("$eq")]
    public EQNode eq { get; set; }
}

public class AndNode
{
    [JsonProperty("$eq")]
    public EQNode eq { get; set; }

    [JsonProperty("$like")]
    public LikeNode like { get; set; }

    [JsonProperty("$gt")]
    public GTNode gt { get; set; }

    [JsonProperty("$lt")]
    public LTNode lt { get; set; }

    [JsonProperty("$or")]
    public List<OrNode> or { get; set; }

    [JsonProperty("$and")]
    public List<OrNode> and { get; set; }
}

public class RootObject
{
    [JsonProperty("$and")]
    public List<AndNode> and { get; set; }

    [JsonProperty("$or")]
    public List<OrNode> or { get; set; }

    [JsonProperty("$eq")]
    public EQNode eq { get; set; }

    [JsonProperty("$like")]
    public LikeNode like { get; set; }

    [JsonProperty("$gt")]
    public GTNode gt { get; set; }

    [JsonProperty("$lt")]
    public LTNode lt { get; set; }
}

Это было сгенерировано "прошлой специальной" функцией визуальной студии (и я добавил весь декоратор JsonProperty, чтобы соответствовать именам в моем json). Это работает, но это не совсем то, что я ожидал.

Во-первых, я хотел бы иметь самую близкую структуру из моей структуры WebApp (с наследованием). Во-вторых, мне не нравится, как этот сгенерированный код обрабатывает тот факт, что у меня может быть $eq, $gt, $lt, $like, $ или или $ и узел в моем корневом объекте, я не могу поверить, что есть нет никакого способа получить чистую структуру, подобную этой: (примечание: это, например, для некоторых целей какой-то используемый декоратор не существует или используется плохо, это только для демонстрации)

public class RootObject {
    public FilterNode root;
} 

public class FilterNode {
}

public class ConjunctionNode: FilterNode {
    public FilterNode[] childs
}

[JsonObject("$and")]
public class AndNode: ConjunctionNode {
}

[JsonObject("$or")]
public class OrNode: ConjunctionNode {
}

[JsonObject("$like")]
public class LikeNode: FilterNode {
    public string key;
    public string value;
}

[JsonObject("$eq")]
public class EQNode: FilterNode {
    public string key;
    public string value;
}

[JsonObject("$gt")]
public class GTNode: FilterNode {
    public string key;
    public string value;
}

[JsonObject("$lt")]
public class LTNode: FilterNode {
    public string key;
    public string value;
}

Итак, если мы используем образец JSON:

{
    "$and": [
        { 
            "$like": { "key": "name", "value": "foo" }
        },
        {
            "$gt": { "key": "price", "value": 15 }
        }
    ]
}  

Я мог бы использовать:

RootObject obj = JsonConvert.DeserializeObject<RootObject>(filters);

С "корневым" членом RootObject в качестве экземпляра AndNode. Этот AndNode будет содержать два объекта в массиве этого дочернего элемента, которые являются экземпляром LikeNode и экземпляром GTNode.

Можно ли это сделать?

1 ответ

Решение

Я подробно рассмотрел вашу проблему. В объектной модели есть недостаток, который вы склонны десериализовать. Также вы не можете использовать прямую десериализацию, так как ключи могут постоянно меняться. Таким образом, вам придется шаг за шагом пройтись по json, обнаружить и сохранить его в словаре или любом другом типе данных по вашему выбору.

Вот предлагаемые классы объектов:

using System.Collections.Generic;

namespace ConsoleApp1
{
    public class RootObject
    {
        public RootObject()
        {
            ConjunctionNode = new List<ConjunctionNode>();
        }

        public List<ConjunctionNode> ConjunctionNode { get; set; }
    }

    public class FilterNode
    {
        public string Key { get; set; }
        public string Value { get; set; }
    }

    public class ConjunctionNode
    {
        public LikeNode Like { get; set; }
        public EQNode Eq { get; set; }
        public GTNode Gt { get; set; }
        public LTNode Lt { get; set; }
    }

    public class AndNode : ConjunctionNode
    {
    }

    public class OrNode : ConjunctionNode
    {
    }

    public class LikeNode : FilterNode
    {
    }

    public class EQNode : FilterNode
    {
    }

    public class GTNode : FilterNode
    {
    }

    public class LTNode : FilterNode
    {
    }
}

Посмотрите, как объекты сделаны. Теперь вам нужно будет шаг за шагом прочитать json, создать бифуркации и сохранить их, а затем прочитать их или прочитать их и вместе создать свой запрос.

Здесь я только начал получать данные, вы можете попробовать один раз, используя этот подход. Если вы столкнулись с трудностями, не стесняйтесь возвращаться, и я могу сделать еще немного работы или найти другие обходные пути.

using Newtonsoft.Json;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            RootObject rootObject = new RootObject();
            string json = @"{ ""$and"": [ { ""$like"": { ""key"": ""name"", ""value"": ""foo"" } }, {""$gt"": { ""key"": ""price"", ""value"": 15 } } ] }  ";
            //var rootObject = JsonConvert.DeserializeObject<RootObject>(json);


            using (var reader = new JsonTextReader(new StringReader(json)))
            {
                while (reader.Read())
                {
                    //Console.WriteLine("{0} - {1} - {2}", reader.TokenType, reader.ValueType, reader.Value);
                    if (reader.TokenType.ToString() == "PropertyName")
                    {
                        //Console.WriteLine("Hi");
                        CreateConjunctionNode(reader, rootObject);
                        //CreateFilterNode(reader, rootObject);
                        //Console.WriteLine(reader.Value);
                    }
                }
            }
        }

        private static void CreateFilterNode(JsonTextReader reader, RootObject rootObject)
        {
            if (reader.Value.ToString() == "$like")
            {
                LikeNode likeNode = new LikeNode();
            }
            else if (reader.Value.ToString() == "$gt")
            {
                GTNode gTNode = new GTNode();
            }
            else if (reader.Value.ToString() == "$lt")
            {
                LTNode lTNode = new LTNode();
            }
            else if (reader.Value.ToString() == "$eq")
            {
                EQNode eQNode = new EQNode();
            }
        }

        private static void CreateConjunctionNode(JsonTextReader reader, RootObject rootObject)
        {
            if (reader.Value.ToString() == "$and")
            {
                rootObject.ConjunctionNode.Add(new AndNode());
            }
            else if (reader.Value.ToString() == "$or")
            {
                rootObject.ConjunctionNode.Add(new OrNode());
            }
        }
    }
}
Другие вопросы по тегам