【问题标题】:How to create JSON from a slash separated String?如何从斜杠分隔的字符串创建 JSON?
【发布时间】:2021-02-23 09:44:32
【问题描述】:

我有一个斜线分隔的字符串格式。

举例

  myDecision/data/buyerDecision/data/buy = food

  myDecision/data/sellerDecision/data/sell = food

我想把它转换成下面的json格式

"myDecision":{
    "data":{
        "buyerDesision":{
            "data":{"buy":"food"}
        },
        "sellerDesision":{
            "data":{"sell":"food"}
        }
    }
}

我尝试了不同的解决方案,但没有奏效。

【问题讨论】:

  • 第一步是将路径列表转换为树/节点。序列化将非常顺利。关于树视图和路径有多个问题。虽然它是另一个上下文,但它可以直接应用在这里
  • Json 只是一个对象的序列化。所以不要关注对象部分,而是关注可以代表这些路径的对象。
  • @touseefkhan4pk 到目前为止,您尝试过什么,您在哪里卡住了?
  • 为什么你有这种斜线分隔的字符串格式?它是一种 XPath,但它不是。它支持数组吗?值(= value)是否总是被解释为字符串?
  • 列表字符串是否总是包含一个共同的根?如果不是,Json 表示会是什么?

标签: c# json .net json.net


【解决方案1】:

解决此问题的一种方法是手工制作JObjects,然后将它们合并。 (这不是最高效的解决方案,因为它会多次重新创建层次结构的某些级别。)

这种方法不同于Benzara Tahar 提出的解决方案:

  • Tahar:使用字符串操作来构造一个有效的 json,然后使用Json.Parse
  • 我的:使用路径遍历来构造JObject 层次结构。

核心逻辑

核心逻辑可以这样实现:

static JObject CreateHierarchy(Queue<string> pathLevels, JObject currentNode)
{
    if (pathLevels.Count == 0) return currentNode;

    var newNode = new JObject(new JProperty(pathLevels.Dequeue(), currentNode));
    return CreateHierarchy(pathLevels, newNode);
}
  • 这是一个递归函数,它构建从最内层到最外层的层次结构。
  • 因此,如果我们使用以下pathLevels 调用此方法:data, buyerDecision, data, myDescision
    那么它会生成这样的东西:
new JObject(
    new JProperty("myDecision",
        new JObject(
            new JProperty("data",
                new JObject(
                    new JProperty("buyerDecision",
                        new JObject(
                            new JProperty("data", currentNode)))))));

调用核心逻辑

首先,将输入字符串转换成字典,其中

  • 关键是路径
  • 该值是所需的最内层值:
var dataSource = new List<string>
{
    "myDecision/data/buyerDecision/data/buy = food",
    "myDecision/data/sellerDecision/data/sell = food"
};

var mappings = dataSource.Select(data => data.Split('=', StringSplitOptions.RemoveEmptyEntries))
    .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
  • 注意:这很脆弱,因为如果多个数据源条目想要设置相同的属性,它可能会抛出 ArgumentException: 'An item with the same key has already been added
  • 我们可以遍历字典,从条目中构造JObjects
    • 密钥应该被/拆分然后反向集合
    • 最里面的值和最里面的路径可以用来构造currentNode
var objectsWithHierarchy = new List<JObject>();
foreach (var (path, innerMostValue) in mappings.Select(kv => (kv.Key, kv.Value)))
{
    var entryLevels = path.Split('/').Reverse().ToArray();
    objectsWithHierarchy.Add(CreateHierarchy(new Queue<string>(entryLevels.Skip(1)),
        new JObject(new JProperty(entryLevels.First(), innerMostValue))));
}

合并对象

在这里,我们从objectsWithHierarchy 中选择第一个对象作为集合的基础JObject,我们在此基础上应用/合并JObjects 的其余部分。

var baseObject = objectsWithHierarchy.First();
var mergeSettings = new JsonMergeSettings {MergeArrayHandling = MergeArrayHandling.Union};
foreach (var currentObj in objectsWithHierarchy.Skip(1))
{
    baseObject.Merge(currentObj, mergeSettings);
}

Console.WriteLine(baseObject);

输出如下:

{
  "myDecision": {
    "data": {
      "buyerDecision": {
        "data": {
          "buy": "food"
        }
      },
      "sellerDecision": {
        "data": {
          "sell": "food"
        }
      }
    }
  }
}

结束的想法

  • 这段代码真的很脆弱,因为它是根据两个样本数据设计的,没有实际要求
  • 数据格式非常独特,所以我们想使用JSONPath之类的东西
  • 实际用例也是未知的,因此解决方案本身的性能可能很差
    • 如果层次结构非常深
    • 如果数据源非常大

【讨论】:

  • 这是一个比我更有弹性的解决方案,我已经记住了(我在我的 OP 中指出了递归方法),但我不想投入太多时间,尤其是询问question 没有费心用他实际尝试过但没用的方法来更新他的问题 (+1)。
  • @BenzaraTahar 我刚刚阅读了您的原始帖子,但没有注意到那一行。 +1 也适合你。
  • 优秀的答案。谢谢。
【解决方案2】:

想到的最简单的解决方案是这样的:

  static async Task Main(string[] args)
    {
        var stringObjects = new string[]
        {
              "myDecision/data/buyerDecision/data/buy = food",
               "myDecision/data/sellerDecision/data/sell = food"
        };
        var objectList = new List<JObject>();
        foreach (var s in stringObjects)
        {
            objectList.Add(ParseStringObject(s));
        }
    }

    static JObject ParseStringObject(string s)
    {
        // TODO! normalize input string (trim white space)

        var openingBrackets = "{"; // top level braket
        var closingBrakets =  new string('}', s.Count(c => c == '/') + 1) ; // +1 for the top level braket
        var json = $"{openingBrackets}\"{s.Replace("/", "\": { \"").Replace("=", "\":\"")}\"{closingBrakets}";
        
        return JObject.Parse(json);
        // TODO! handle exceptions 

    }

您需要安装 Newtonsoft.Json 才能正常工作。

不过,其他递归方法也是可能的。

更新: 要将所有字符串转换为单个 Object,可以使用 JObject 支持的 Merge 方法

  static async Task Main(string[] args)
    {
        var stringObjects = new string[]
        {
              "myDecision/data/buyerDecision/data/buy = food",
               "myDecision/data/sellerDecision/data/sell = food"
        };
        var resultingObject = new JObject();
        var mergeSettings = new JsonMergeSettings
        {
            MergeArrayHandling = MergeArrayHandling.Union
        };
        foreach (var s in stringObjects)
        {
            var obj = ParseStringObject(s);
            resultingObject.Merge(obj,mergeSettings);
        }
        Console.WriteLine(resultingObject.ToString(Newtonsoft.Json.Formatting.Indented));
    }

    static JObject ParseStringObject(string s)
    {
        try
        {
           //remove whitespace
            s = Regex.Replace(s, @"\s+", "");
            var openingBrackets = "{"; // top level braket
            var closingBrakets = new string('}', s.Count(c => c == '/') + 1); // +1 for the top level braket
            var json = $"{openingBrackets}\"{s.Replace("/", "\": { \"").Replace("=", "\":\"")}\"{closingBrakets}";

            return JObject.Parse(json);
        }
        catch (Exception)
        {
            // return an empty object
            return new JObject();
        }
    }

这应该输出:

【讨论】:

  • OP 想要这两个字符串中的一个对象。
  • 你有 JObject,你可以将它反序列化为任何你想要的类对象
  • 问题是最终属性不固定。 “买”“卖”
  • 这不是问题。问题是您的代码返回两个对象,而不是 OP 在其所需输出中显示的一个对象。
  • 是的,你是对的,我很快就误读了这个问题,尽管他想要一个对象数组。我会更新答案
猜你喜欢
  • 2021-04-28
  • 1970-01-01
  • 2012-10-23
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多