使用具有可变内容的C#反序列化Json [英] Deserialize Json using C# with variable content

查看:58
本文介绍了使用具有可变内容的C#反序列化Json的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要重新发布此问题,因为有人将其标记为重复,但事实并非如此,因此我详细介绍了以避免混淆.

我正在用c#创建一个API,我的WebApp可以访问它以获取一些数据.在我的Web应用程序中,我有一个包含(比如说)产品列表的视图.现在,我有一条路线可以返回我存储在数据库中的所有产品.

我现在想为webapp添加制作复杂过滤器的功能.我希望能够在webApp中或与Postman为例,使用名为"filters"的URlParam到达"/api/product"的路由(例如),其中将包含webapp需要应用的所有过滤器.

现在回到我的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关键字,女巫无效.

我想做什么: 在一个美丽的世界中,我希望能够在我的Web应用程序中序列化此json,将其转换为有效的URL参数字符串,然后将其发送到API(c#)反序列化该JSON,以获得与我相同的对象结构.在WebApp上. 对于带有价格和名称的示例,我想有一个AndNode类的实例,其成员"child"包含2个对象:LikeNode类的1个实例和GTNode类的1个实例.

此实现可能值得商,,但这是我们现在需要的.

现在,我有:

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; }
}

这是由visual studio的过去特殊"功能生成的(我添加了所有JsonProperty装饰器以匹配json中的名称). 它有效,但是,这并不是我真正期望的.

首先,我想拥有与WebApp结构最接近的结构(带有继承). 其次,我不喜欢这种生成的代码处理以下事实的方式:我可以在根对象中包含$ eq,$ gt,$ lt,$ like,$ or或$ and节点,我不敢相信没有办法像这样一个干净的结构: (注意:例如,这是出于某些装饰器不存在或使用不当的目的,这仅用于演示)

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的"root"成员作为AndNode的实例. 这个AndNode会在此子数组中包含两个对象,分别是LikeNode的实例和GTNode的实例.

可以做到吗?

解决方案

我详细介绍了您的问题.您倾向于反序列化的对象模型中存在缺陷.同样,您不能使用直接反序列化,因为密钥可能会不断变化.因此,您将必须逐步检查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());
            }
        }
    }
}

I'm reposting this question because someone marked it as duplicate but it's not, so I detailed more to avoid the confusion.

I'm creating an API in c# that my WebApp can reach to get some data. In my web app, i have a view with a list of (let's say) products. For now i have a route that return all my products stored in Database.

I would like now to add the ability for the webapp to make complexe filters. I would like to be able in the webApp or with Postman for exemple, to reach my route (let's say) "/api/product" with a URlParam named "filters" that will contains all filters that the webapp need to apply.

Now back to my WebApp (build with angular) I have a service that can create like a tree of filters (See class architecture below). (And yes this architecture could be improved)

The idea is that i can build filter like this : If i want to fetch all Product that have "foo" in their names and with a price gretter than 15$ :

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

Will result in this Json :

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

Now, this Json have 1 requirement : It should never contains more than one property at is first level. Because this is meant to be used to generate SQL Filters, it make sense that, the json must contains only one node on his first level : an EQ, LIKE, GT, or LT node, or if we have more than one filters an AND / OR nodes that define the logical "conjunction" between each filters of the next level.

So it makes sense that :

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

Is valid and could result in this SQL statement :

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

Also :

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

Is valid and could result as this sql statement :

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

But :

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

Is not valid because it will result a this sql statement :

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

Witch is not valid because of the missing AND or OR keyword.

What I would like to do : In a beautiful world, I would like to be able to serialize this json in my web app, convert it to a valid URL param string, and send it to the API (c#) deserialize that JSON to get the same object structure that I had on the WebApp. For our example with the price and the name, I would like to have an instance of the AndNode class with a member "child" that contains 2 object: 1 instance of the LikeNode class and 1 instance of the GTNode class.

This implementation is maybe debatable, but this is what we need for now.

For now, I have :

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; }
}

This has been generated by the "past special" feature of visual studio (and I added all the JsonProperty decorator to match the names in my json). It works, but, this is not really what I expected.

First, i would like to have the closest structure from my WebApp structure (with inheritance). Second, i don't like the way this generated code handle the fact that i can have either $eq, $gt, $lt, $like, $or or $and node in my root object, i can't believe there is no way to have a clean structure like that : (note: this is for example purpose some decorator used doesn't exist or are badly used, this is just for demo)

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;
}

So, if we use the sample json :

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

I could use :

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

With the "root" member of the RootObject as an instance of the AndNode. This AndNode would contain two objects in this child's array, which are an instance of the LikeNode and an instance of the GTNode.

Can this be done?

解决方案

I went through your problem in detail. There is flaw in the object model to which you tend to deserialize. Also you cant use direct deserialization as the keys may keep changing. So you would have to go through the json step by step and detect and store it in a dictionary or any data type of your choice.

Here is the proposed object classes:

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
    {
    }
}

See how the objects are made. Now you will need to read the json step by step and create bifurcations and store then and then read them, or read them and create your query on the go together.

Here I just started to get the data, you can try once using this approach. If you face difficulty, feel free to come back and I can do some more work or figure out other workarounds.

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());
            }
        }
    }
}

这篇关于使用具有可变内容的C#反序列化Json的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆