【问题标题】:Deserialize Json using C# with variable content使用具有可变内容的 C# 反序列化 Json
【发布时间】:2017-11-20 14:52:30
【问题描述】:

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

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

我现在想为 web 应用添加制作复杂过滤器的功能。例如,我希望能够在 webApp 或 Postman 中使用名为“filters”的 URlParam 到达我的路线(比方说)“/api/product”,该 URlParam 将包含 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 / OR 节点,定义下一级的每个过滤器之间的逻辑“连接”。

所以这是有道理的:

{
    "$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

Witch 无效,因为缺少 AND 或 OR 关键字。

我想做的事: 在一个美丽的世界里,我希望能够在我的网络应用程序中序列化这个 json,将其转换为有效的 URL 参数字符串,并将其发送到 API (c#) 反序列化该 JSON 以获得与我相同的对象结构在 Web 应用程序上。 对于我们的带有价格和名称的示例,我想要一个 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的“根”成员作为AndNode的一个实例。 这个 AndNode 将包含这个子数组中的两个对象,它们是 LikeNode 的一个实例和 GTNode 的一个实例。

这个可以吗?

【问题讨论】:

  • 您最初问题的答案有什么问题?
  • 我原来的帖子不太详细。但是有回应:使用“过去的特殊功能”是一个很好的建议,而其他答案是让我详细说明。所以现在我现在有答案来解决这个问题。
  • 您应该更新初始问题,而不是发布新问题。 @angsuman-ray 还要求提供详细的 JSON,从外观上看,您还没有回复他们。
  • 我在回答他们之后立即编辑了我的帖子,我会这样做。我的更新位于“编辑 1:”下方。这是在编辑我的帖子之后,我看到帖子被“锁定”(重复)。此外,我的问题的两个版本之间的帖子令人困惑。我决定用(我希望)更清楚的解释重新打字。我将在我的旧帖子中引用这篇文章并要求关闭我的第一个

标签: c# .net json deserialization


【解决方案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());
            }
        }
    }
}

【讨论】:

  • 您好,感谢您的回答。我试图避免“手动”反序列化,但由于无法以其他方式完成,我会这样做。这会在想要的对象结构中反序列化我的对象。正是我需要的最终结果。谢谢!
  • 太棒了。始终追求事物的松散耦合。使东西模块化和可重用。
猜你喜欢
  • 2021-06-08
  • 1970-01-01
  • 1970-01-01
  • 2020-04-09
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-05
相关资源
最近更新 更多