Как сериализовать / десериализовать словарь JSON в массив - PullRequest
3 голосов
/ 20 мая 2019

Необходимо сериализовать и десериализовать словари C # в массив JSON.Я также хотел бы прочитать JSON из powershell, используя индексную запись массива.

По умолчанию формат JSON:

{
 "defaultSettings": {
  "applications": {
   "Apollo": {
    "environments": {
      "DEV": {
        "dbKeyTypes": {
          "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06",
          "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
        }
      },
      "TST": {
        "dbKeyTypes": {
          "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06",
          "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
        }
      }
    }
  },
  "Gemini": {
    "environments": {
      "DEV": {
        "dbKeyTypes": {
          "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06",
          "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
        }
      },
      "TST": {
        "dbKeyTypes": {
          "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06",
          "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
        }
      }
    }
   }
  }
 }
}

Это прекрасно работает при использовании программы чтения json по умолчанию в .Net Core,но это не позволяет мне использовать обозначение индекса массива в PowerShell.

Вместо этого я ищу следующее:

{
 "defaultSettings": {
  "applications": [
   {
     "Apollo": {
      "environments": [
        {
          "DEV": {
            "dbKeyTypes": [
              {
                "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06"
              },
              {
                "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
              }
            ]
          }
        },
        {
          "TST": {
            "dbKeyTypes": [
              {
                "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06"
              },
              {
                "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
              }
            ]
          }
        }
      ]
    }
  },
  {
    "Gemini": {
      "environments": [
        {
          "DEV": {
            "dbKeyTypes": [
              {
                "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06"
              },
              {
                "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
              }
            ]
          }
        },
        {
          "TST": {
            "dbKeyTypes": [
              {
                "DmkPassword": "AEikOooIuGxXC9UBJQ3ckDj7Q126tB06"
              },
              {
                "SymmetricKeySource": "bTU7XOAYA2FFifmiBUggu99yHxX3Ftds"
              }
            ]
          }
        }
      ]
    }
   }
  ]
 }
}

Я использую часть WriteJson из Сериализующий словарьв массив "name": "value"

Это хорошо работает;однако, конечно, поскольку метод ReadJson() не реализован, он не читает.Кстати, чтобы получить вышеуказанный желаемый формат json, я изменил CustomDictionaryConverter в ссылке:

writer.WritePropertyName(key.ToString());
//writer.WriteValue(key);
//writer.WritePropertyName("value");
serializer.Serialize(writer, valueEnumerator.Current);

Классы, стоящие за реализацией:

public enum DeploymentEnvironment { DEV = 1, TST = 2 }
public enum TargetApplication { Apollo = 1, Gemini = 2 }
public enum DbKeyType { DmkPassword = 1, SymmetricKeySource = 2 }

public class DeploymentSettings
{
    [JsonProperty("defaultSettings")]
    public DefaultSettings DefaultSettings { get; set; }
    public DeploymentSettings()
    {
        DefaultSettings = new DefaultSettings();
    }
}

public partial class DefaultSettings
{
    [JsonProperty("applications")]
    public Dictionary<TargetApplication, ApplicationContainer> Applications { get; set; }

    public DefaultSettings()
    {
        Applications = new Dictionary<TargetApplication, ApplicationContainer>();
    }
}

public partial class ApplicationContainer
{
    [JsonProperty("environments")]
    public Dictionary<DeploymentEnvironment, EnvironmentContainer> Environments { get; set; }
    public ApplicationContainer()
    {
        Environments = new Dictionary<DeploymentEnvironment, EnvironmentContainer>();
    }
}

public partial class EnvironmentContainer
{
    [JsonProperty("dbKeyTypes")]
    public Dictionary<DbKeyType, string> DbKeyTypes { get; set; }

    public EnvironmentContainer()
    {
        DbKeyTypes = new Dictionary<DbKeyType, string>();
    }
}

Я сериализую объектследующим образом: var json = JsonConvert.SerializeObject(ds, Formatting.Indented, new CustomDictionaryConverter());

Как уже упоминалось, сериализация работает, но мне нужна помощь в написании метода ReadJson() для возможности десериализации.

1 Ответ

0 голосов
/ 21 мая 2019

Вы можете расширить CustomDictionaryConverter для чтения и записи следующим образом:

public class CustomDictionaryConverter : JsonConverter
{
    // Adapted from CustomDictionaryConverter from this answer https://stackoverflow.com/a/40265708
    // To https://stackoverflow.com/questions/40257262/serializing-dictionarystring-string-to-array-of-name-value
    // By Brian Rogers https://stackoverflow.com/users/10263/brian-rogers

    sealed class InconvertibleDictionary : Dictionary<object, object>
    {
        public InconvertibleDictionary(DictionaryEntry entry)
            : base(1)
        {
            this[entry.Key] = entry.Value;
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary).IsAssignableFrom(objectType) && objectType != typeof(InconvertibleDictionary);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Lazy evaluation of the enumerable prevents materialization of the entire collection of dictionaries at once.
        serializer.Serialize(writer,  Entries(((IDictionary)value)).Select(p => new InconvertibleDictionary(p)));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        var dictionary = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                serializer.Populate(reader, dictionary);
                return dictionary;

            case JsonToken.StartArray:
                {
                    while (true)
                    {
                        switch (reader.ReadToContentAndAssert().TokenType)
                        {
                            case JsonToken.EndArray:
                                return dictionary;

                            case JsonToken.StartObject:
                                serializer.Populate(reader, dictionary);
                                break;

                            default:
                                throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
                        }
                    }
                }

            default:
                throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
        }
    }

    static IEnumerable<DictionaryEntry> Entries(IDictionary dict)
    {
        foreach (DictionaryEntry entry in dict)
            yield return entry;
    }
}

public static partial class JsonExtensions
{
    public static JsonReader ReadToContentAndAssert(this JsonReader reader)
    {
        return reader.ReadAndAssert().MoveToContentAndAssert();
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

Затем вы можете сериализовать и десериализовать ваш DeploymentSettings со следующими настройками:

var settings = new JsonSerializerSettings
{
    Converters = { new CustomDictionaryConverter(), new StringEnumConverter() }
};

var ds = JsonConvert.DeserializeObject<DeploymentSettings>(json, settings);

var json2 = JsonConvert.SerializeObject(ds, Formatting.Indented, settings);

Примечания:

  • Эта версия конвертера не позволяет загружать весь словарь во временную иерархию JArray в ReadJson() или WriteJson(), а вместо этого направляет потоки непосредственно из и вПоток JSON.

  • Поскольку сериализатор теперь используется для прямой сериализации отдельных записей словаря, для правильного именования ключей требуется StringEnumConverter.(Использование сериализатора также гарантирует, что числовые или DateTime ключи правильно интернационализированы, если вы будете использовать такой словарь где угодно.)

  • Поскольку Json.NET поддерживает комментарии, конвертер проверяет наличиеи пропускает их, что добавляет сложности.(Хотелось бы, чтобы был способ заставить JsonReader молча пропускать комментарии.)

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

...