Рекурсивный анализ JSON с использованием JSON.NET - PullRequest
1 голос
/ 03 октября 2019

У меня следующий JSON-объект, возвращенный из JSON, отличается:

{
    "lastName": ["Bab", "Beb"],
    "middleName": ["Cg", "seeg"],
    "contact":
    {
        "emailAddress": ["bab@example.com", "beb@example.com"],
        "addresses":
        [
            {
                "state": ["AL", "AZ"]
            },
            {
                "state": ["TN", "MO"]
            }
        ]
    }
}

Мне нужен список изменений следующим образом.

lastName/new:Bab/old:Beb
middleName/new:Cg/old:seeg
contact.emailAddress/new:bab@example.com/old:beb@example.com
contact.addresses[0].state/new:AL/old:AZ
contact.addresses[1].state/new:TN/old:MO

Итак, я написал эту уродливую программуиспользуя немного рекурсии.

private static IEnumerable<DocumentProperty> ParseJObject(JObject node)
{
    HashSet<DocumentProperty> documentProperties = new HashSet<DocumentProperty>();
    DocumentProperty documentProperty = new DocumentProperty();

    foreach (KeyValuePair<string, JToken> sub in node)
    {
        if (sub.Value.Type == JTokenType.Array)
        {
            // unnamed nodes which contain nested objects 
            if (sub.Value.First.Type == JTokenType.Object)
            {
                foreach (var innerNode in sub.Value.Children())
                {
                    documentProperties.UnionWith(ParseJObject((JObject)innerNode));
                }
            }

            documentProperty = CreateDocumentProperty(sub.Value);
        }
        else if (sub.Value.Type == JTokenType.Object)
        {
            documentProperties.UnionWith(ParseJObject((JObject)sub.Value));
        }

        documentProperties.Add(documentProperty);
    }

    return documentProperties;
}

Это сработало, за исключением того, что оно дает мне дополнительный вывод.

lastName/new:Bab/old:Beb
middleName/new:Cg/old:seeg
contact.emailAddress/new:bab@example.com/old:beb@example.com
contact.addresses[0].state/new:AL/old:AZ
contact.addresses[1].state/new:TN/old:MO
contact.addresses/new:{                <-----------------------------Extra here.
  "state": [
    "AL",
    "AZ"
  ]
}/old:{
  "state": [
    "TN",
    "MO"
  ]
}

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

Определение для CreateDocumentProperty

private static DocumentProperty CreateDocumentProperty(JToken subValue) => new DocumentProperty()
{
    PropertyName = subValue.Path,
    New = subValue[0].ToString(),
    Old = subValue[1].ToString()
};

Основной метод:

static void Main()
{
    JToken jToken = JToken.Parse("{\"lastName\":[\"Bab\",\"Beb\"],\"middleName\":[\"Cg\",\"seeg\"],\"contact\":{\"emailAddress\":[\"bab@example.com\",\"beb@example.com\"],\"addresses\":[{\"state\":[\"AL\",\"AZ\"]},{\"state\":[\"TN\",\"MO\"]}],}}");

    JObject inner = jToken.Value<JObject>();
    IEnumerable<DocumentProperty> data = ParseJObject(inner);

    foreach (var item in data) Console.WriteLine(item);
}

1 Ответ

1 голос
/ 04 октября 2019

Вместо написания собственного рекурсивного кода вы можете использовать JContainer.DescendantsAndSelf(), чтобы найти все новые пары значений / старых значений, а затем преобразовать их в строку с требуемым форматированием с помощью LINQ.

Сначала определите следующий метод расширения:

public static IEnumerable<string> GetDiffPaths(this JContainer root)
{
    if (root == null)
        throw new ArgumentNullException(nameof(root));
    var query = from array in root.DescendantsAndSelf().OfType<JArray>()
                where array.Count == 2 && array[0] is JValue && array[1] is JValue
                select $"{array.Path}/new:{array[0]}/old:{array[1]}";
    return query;
}

А затем выполните:

var jContainer = jToken as JContainer;
if (jContainer == null)
    throw new JsonException("Input was not a container");

foreach (var item in jContainer.GetDiffPaths())
{
    Console.WriteLine(item);
}

Демонстрационная скрипка здесь .

Примечания:

  1. В приведенном выше коде я просто генерирую перечислимые строки, но вы можете заменить это перечислимым DocumentProperty объектами (которые не были полностью включены в ваш вопрос).

  2. Я предполагаю, что любой массив JSON, содержащий два ровно два примитивных значения, представляет пару новое значение / старое значение.

    В вашем коде проверка на это не выполнена правильно,В частности, я считаю, что как минимум отсутствует else в следующем месте:

    foreach (KeyValuePair<string, JToken> sub in node)
    {
        if (sub.Value.Type == JTokenType.Array)
        {
            // unnamed nodes which contain nested objects 
            if (sub.Value.First.Type == JTokenType.Object)
            {
                foreach (var innerNode in sub.Value.Children())
                {
                    documentProperties.UnionWith(ParseJObject((JObject)innerNode));
                }
            }
            else // ELSE WAS REQUIRED HERE
            {
                documentProperties.Add(CreateDocumentProperty(sub.Value));
            }
        }
        else if (sub.Value.Type == JTokenType.Object)
        {
            documentProperties.UnionWith(ParseJObject((JObject)sub.Value));
        }
    }
    

    Демонстрационная скрипка # 2 здесь .

  3. JContainer представляет либо массив JSON, либо объект JSON. Я предполагаю, что процедура diff должна возвращать одно или другое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...