Как я могу объединить JSON свойств с именами, соответствующими некоторому шаблону, в свойство, имеющее значение массива? - PullRequest
1 голос
/ 21 июня 2020

Как объединить объект с одним и тем же ключом, игнорируя оболочку массива ([])?

Строки ниже примера имеют свойства с именами "elements[0]", "elements[1]" и "elements[2]".

  • Свойства могут быть на любом уровне структуры JSON.
  • более одного элемента массива, например, elements [0], [1], [2], [3] и anotherelement [0] , [1], [2], [3]
{
   "addresses":[
      "some address"
   ],
   "rows":[
      {
         "elements[0]":{
            "distance":{
               "text":"227 mi",
               "value":365468
            },
            "duration":{
               "text":"3 hours 54 mins",
               "value":14064
            },
            "status":"OK"
         },
         "elements[1]":{
            "distance":{
               "text":"94.6 mi",
               "value":152193
            },
            "duration":{
               "text":"1 hour 44 mins",
               "value":6227
            },
            "status":"OK"
         },
         "elements[2]":{
            "distance":{
               "text":"2,878 mi",
               "value":4632197
            },
            "duration":{
               "text":"1 day 18 hours",
               "value":151772
            },
            "status":"OK"
         }
      }
   ],
   "status":[
      "OK"
   ]
}

Ожидается элемент [{element0, element1, element2}].

{
   "addresses":[
      "some address"
   ],
   "rows":[
      {
      "elements": [{
        "distance": {
          "text": "227 mi",
          "value": 365468
        },
        "duration": {
          "text": "3 hours 54 mins",
          "value": 14064
        },
        "status": "OK"
      },
      {
        "distance": {
          "text": "94.6 mi",
          "value": 152193
        },
        "duration": {
          "text": "1 hour 44 mins",
          "value": 6227
        },
        "status": "OK"
      },
      {
        "distance": {
          "text": "2,878 mi",
          "value": 4632197
        },
        "duration": {
          "text": "1 day 18 hours",
          "value": 151772
        },
        "status": "OK"
      }]}
   ],
   "status":[
      "OK"
   ]
}

Требуется в строке unknown JSON, поэтому не может создать класс / модель. Выше приведен пример, любой общий код c будет более полезным.

Обновлено :: Спасибо @ db c. Это выглядит многообещающе, элементы порядка обязательно будут возрастать, однако другие ключи меняются местами

{  
    "elements[0]": "Value 0",
    "elements[1]": "Value 1",
    "anotherelement[0]": "Another value 0",
    "anotherelement[1]": "Another value 1",
    "status" : "OK",
    "lastitem" : "yes"
}

результат такой, как показано ниже. Можно ли как есть порядок вещей. Я знаю, что в JSON это не повлияет, но просто хочу посмотреть, возможно ли

{
    "status" : "OK",
    "lastitem" : "yes",
    "elements": [
        "Value 0",
        "Value 1"
    ],
    "anotherelement": [
        "Another value 0",
        "Another value 1"
    ]
}

ожидается

{
    "elements": [
        "Value 0",
        "Value 1"
    ],
    "anotherelement": [
        "Another value 0",
        "Another value 1"
    ],
    "status" : "OK",
    "lastitem" : "yes"
}

1 Ответ

1 голос
/ 21 июня 2020

Чтобы переформулировать вашу проблему, у вас есть произвольная JSON иерархия, которая содержит свойства, имена которых заканчиваются числовыми индексами в скобках, как показано ниже (где значения могут быть любого типа):

{
    "elements[0]": "Value 0",
    "elements[1]": "Value 1",
    "anotherelement[0]": "Another value 0",
    "anotherelement[1]": "Another value 1"
}

И вы хотели бы преобразовать их в свойства с массивами, удалив индексы в квадратных скобках, а также сгруппировав и объединив все значения с идентичными удаленными именами свойств, например:

{
    "elements": [
        "Value 0",
        "Value 1"
    ],
    "anotherelement": [
        "Another value 0",
        "Another value 1"
    ]
}

Это можно сделать, используя LINQ to JSON для редактирования иерархии JSON. Вам также нужно будет использовать регулярное выражение для выбора совпадающих имен свойств и оператор LINQ group для группировки элементов с похожими именами.

Работу выполняет следующий метод расширения:

public static partial class JsonExtensions
{
    public static void FixElementArrays(this JToken root)
    {
        var regex = new Regex("^(.+)\\[[0-9]+\\]$");
        if (root is JContainer container)
        {
            var query = 
                from o in container.DescendantsAndSelf().OfType<JObject>()
                let matches = o.Properties()
                    .Select(p => (Property : p, Match : regex.Match(p.Name)))
                    .Where(m => m.Match.Success)
                    .Select(m => (m.Property, Name : m.Match.Groups[1].Value))
                let groups = matches.GroupBy(m => m.Name)
                from g in groups
                select (Object : o, Name : g.Key, Values : g.Select(m => m.Property.Value).ToList());
            foreach (var g in query.ToList())
            {
                IList<JToken> objAsList = g.Object;
                // DescendantsAndSelf() returns items in document order, which ordering is preserved by GroupBy, so index of first item should be first index.
                var insertIndex = objAsList.IndexOf(g.Values[0].Parent);
                g.Values.ForEach(v => v.RemoveFromLowestPossibleParent());
                objAsList.Insert(insertIndex, new JProperty(g.Name, new JArray(g.Values)));
            }
        }           
    }
    
    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 property = node.Parent as JProperty;
        var contained = property ?? node;
        if (contained.Parent != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (property != null)
            property.Value = null;
        return node;
    }
}

Что вы можете использовать следующим образом:

var rootToken = JToken.Parse(jsonString);
rootToken.FixElementArrays();
var fixedJsonString = rootToken.ToString();

Примечания:

  • Возможно, вам потребуется настроить регулярное выражение на основе вашего Фактические JSON имена свойств. Например, из вашего вопроса неясно, что делать с таким именем, как "[0][1]".

  • Код предполагает, что свойства "elements[*]" уже находятся в правильном порядке, т.е. не

    {
        "elements[3]": "Value 3",
        "elements[1]": "Value 1",
        "elements[2]": "Value 2"
    }
    

    И поэтому помещает их в последний массив в том порядке, в котором они встречаются, вместо того, чтобы пытаться упорядочить его по номеру индекса внутри имени свойства "elements[*]".

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

...