Ваша основная проблема в том, что в PrintObject(JToken token, int depth)
вы не учитываете случай, когда входящий token
является JArray
:
if (token is JProperty)
{
}
else if (token is JObject)
{
}
// Else JArray, JConstructor, ... ?
Поскольку значение "allOf"
является массивом, ваш код ничего не делает:
{
"allOf": [ /* Contents omitted */ ]
}
Минимальным исправлением будет проверка JContainer
вместо JObject
, однако это не относится к случаю массива, содержащего примитивные значения, и поэтому не может считаться правильным исправлением. (Демонстрационная скрипка № 1 здесь .)
Вместо этого в вашем рекурсивном коде вам нужно обрабатывать все возможные подклассы JContainer
, включая JObject
, JArray
, JProperty
и (возможно) JConstructor
. Однако несоответствие между JObject
, имеющим два уровня иерархии, и JArray
, имеющим только один, может раздражать написание такого рекурсивного кода.
Одним из возможных решений более чистой обработки массивов и объектов было бы скрыть существование JProperty
полностью и представить, что объекты - это контейнеры, чьи дочерние элементы индексируются по имени, а массивы - это контейнеры, чьи дочерние элементы индексируются целыми числами. Следующий метод расширения делает эту работу:
public interface IJTokenWorker
{
bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible;
}
public static partial class JsonExtensions
{
public static void WalkTokens(this JToken root, IJTokenWorker worker, bool includeSelf = false)
{
if (worker == null)
throw new ArgumentNullException();
DoWalkTokens<int>(null, -1, root, worker, 0, includeSelf);
}
static void DoWalkTokens<TConvertible>(JContainer parent, TConvertible index, JToken current, IJTokenWorker worker, int depth, bool includeSelf) where TConvertible : IConvertible
{
if (current == null)
return;
if (includeSelf)
{
if (!worker.ProcessToken(parent, index, current, depth))
return;
}
var currentAsContainer = current as JContainer;
if (currentAsContainer != null)
{
IList<JToken> currentAsList = currentAsContainer; // JContainer implements IList<JToken> explicitly
for (int i = 0; i < currentAsList.Count; i++)
{
var child = currentAsList[i];
if (child is JProperty)
{
DoWalkTokens(currentAsContainer, ((JProperty)child).Name, ((JProperty)child).Value, worker, depth+1, true);
}
else
{
DoWalkTokens(currentAsContainer, i, child, worker, depth+1, true);
}
}
}
}
}
Тогда ваш Convert()
метод теперь становится:
class JTokenPrinter : IJTokenWorker
{
public bool ProcessToken<TConvertible>(JContainer parent, TConvertible index, JToken current, int depth) where TConvertible : IConvertible
{
var spacer = new String('\t', depth);
string name;
string val;
if (parent != null && index is int)
name = string.Format("[{0}]", index);
else if (parent != null && index != null)
name = index.ToString();
else
name = "";
if (current is JValue)
val = ((JValue)current).ToString();
else if (current is JConstructor)
val = "new " + ((JConstructor)current).Name;
else
val = "-";
Console.WriteLine(string.Format("{0}{1} -> {2}", spacer, name, val));
return true;
}
}
public static void Convert(string json)
{
var root = JsonConvert.DeserializeObject<JToken>(json);
root.WalkTokens(new JTokenPrinter());
}
Демонстрационная скрипка # 2 здесь , которая выводит:
allOf -> -
[0] -> -
field -> type
equals -> Microsoft.KeyVault/vaults
[1] -> -
anyOf -> -
[0] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
exists -> false
[1] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.virtualNetworkRules[*].id
notLike -> *
[2] -> -
field -> Microsoft.KeyVault/vaults/networkAcls.defaultAction
equals -> Allow
Связанный: