Как я могу сериализовать PSObjects в C # с JSON.NET? - PullRequest
0 голосов
/ 01 сентября 2018

Я пишу командлет и мне нужно передать объектные структуры в клиент API, который может содержать PSObject s. В настоящее время они сериализуются в виде строки JSON, содержащей CLIXML. Вместо этого мне нужно обрабатывать его как объект (включая свойства NoteProperties в PSObject.Properties как свойства и рекурсивно сериализовать их значения).

Я попытался написать свой собственный JsonConverter, но по какой-то причине он вызывается только для объекта верхнего уровня, а не для вложенных PSObject s:

public class PSObjectJsonConverter : JsonConverter {

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        if (value is PSObject) {
            JObject obj = new JObject();
            foreach (var prop in ((PSObject)value).Properties) {
                obj.Add(new JProperty(prop.Name, value));
            }
            obj.WriteTo(writer);
        } else {
            JToken token = JToken.FromObject(value);
            token.WriteTo(writer);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        throw new NotImplementedException();
    }

    public override bool CanRead {
        get { return false; }
    }

    public override bool CanConvert(Type objectType) {
        return true;
    }
}

Кроме того, я использую сериализацию для случая верблюда, используя CamelCasePropertyNamesContractResolver. Есть ли способ заставить конвертер уважать это?

1 Ответ

0 голосов
/ 01 сентября 2018

Следующий преобразователь должен корректно сериализовать рекурсивно-вложенные объекты типа PSObject:

public class PSObjectJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(PSObject).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var psObj = (PSObject)value;
        writer.WriteStartObject();
        foreach (var prop in psObj.Properties)
        {
            //Probably we shouldn't try to serialize a property that can't be read.
            //https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
            if (!prop.IsGettable)
                continue;           
            writer.WritePropertyName(prop.Name);
            serializer.Serialize(writer, prop.Value);
        }
        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanRead { get { return false; } }
}

Примечания:

  • В WriteJson вы сериализуете входящий объект value в качестве значения каждого свойства. Конечно, вы имели в виду prop.Value.

  • Возвращая true из CanConvert() только тогда, когда тип входящего объекта имеет тип PSObject, вы избегаете необходимости реализовывать сериализацию по умолчанию для типов не PSObject в WriteJson().

  • Когда вы звоните JToken.FromObject(value), вы не используете входящий JsonSerializer serializer. Таким образом, любые JsonSerializerSettings (включая конвертеры) будут потеряны. Теоретически вы можете использовать JToken.FromObject(Object, JsonSerializer) вместо этого, что сохранит настройки, но если вы это сделаете, вы столкнетесь с ошибкой, описанной в JSON.Net выдает исключение StackOverflowException при использовании [JsonConvert () ] . К счастью, поскольку теперь мы возвращаем false из CanConvert, когда требуется сериализация по умолчанию, в этом больше нет необходимости.

  • Нет необходимости строить промежуточное звено JObject. Вы можете написать прямо на JsonWriter, что будет несколько более производительным.

Обновление : Кроме того, я использую сериализацию для случая верблюда, используя CamelCasePropertyNamesContractResolver. Есть ли способ заставить конвертер уважать это?

После того как вы введете custom JsonConverter для вашего типа, вам нужно будет сделать все вручную, включая переназначение имен свойств. Вот версия WriteJson(), которая обрабатывает это с помощью DefaultContractResolver.NamingStrategy:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var psObj = (PSObject)value;
        writer.WriteStartObject();
        var resolver = serializer.ContractResolver as DefaultContractResolver;
        var strategy = (resolver == null ? null : resolver.NamingStrategy) ?? new DefaultNamingStrategy();

        foreach (var prop in psObj.Properties)
        {
            //Probably we shouldn't try to serialize a property that can't be read.
            //https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
            if (!prop.IsGettable)
                continue;
            writer.WritePropertyName(strategy.GetPropertyName(prop.Name, false));
            serializer.Serialize(writer, prop.Value);
        }
        writer.WriteEndObject();
    }

Обратите внимание, что стратегии именования были введены в Json.NET 9.0.1 , поэтому, если вы используете более раннюю версию, вам нужно будет создать свой собственный преобразователь имени случая верблюда, такой как показанный в этот ответ .

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