Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I serialise PSObjects in C# with JSON.NET?

I am writing a Cmdlet and need to pass object structures into an API client that may contain PSObjects. Currently, these serialise as a JSON string containing CLIXML. Instead, I need it to be treated like an object (including the NoteProperties in PSObject.Properties as properties, and recursively serialising their values).

I tried writing my own JsonConverter but for some reason it only gets called for the top level object, not for nested PSObjects:

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;
    }
}

Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?

like image 829
felixfbecker Avatar asked Sep 01 '18 15:09

felixfbecker


People also ask

How can you prevent objects being serializable?

There are several methods for preventing a field from being serialized: Declare the field as private transient. Define the serialPersistentFields field of the class in question, and omit the field from the list of field descriptors.

How do you serialize and deserialize an object in C++?

Once this serialized object information is written to a file, it can be read back, deserialized, and reconstruct the object in memory at any time. The C++ Standard Library is not equipped with the built-in mechanism to serialize or deserialize an object. However, we can use a third-party library and there are many.

Which class contains the method to serialise object manually using code?

Contains methods for serializing Apex objects into JSON format and deserializing JSON content that was serialized using the serialize method in this class.


1 Answers

The following converter should correctly serialize recursively nested objects of type 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://learn.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; } }
}

Notes:

  • In WriteJson you serialize the incoming object value as the value of each property. Surely you meant prop.Value.

  • By only returning true from CanConvert() when the incoming object type is of type PSObject, you avoid the need to implement default serialization for non-PSObject types in WriteJson().

  • When you call JToken.FromObject(value) you are not using the incoming JsonSerializer serializer. Thus, any JsonSerializerSettings (including converters) will be lost. In theory you could use JToken.FromObject(Object, JsonSerializer) instead, which would preserve settings, but if you did, you would encounter the bug described in JSON.Net throws StackOverflowException when using [JsonConvert()]. Luckily, since we now return false from CanConvert when default serialization is required, this is no longer necessary.

  • There is no need to construct an intermediate JObject. You can write directly to the JsonWriter, which will be somewhat more performant.

Update: Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?

Once you introduce a custom JsonConverter for your type, you need to do everything manually, including remapping of property names. Here's a version of WriteJson() that handles this by using 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://learn.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();
    }

Note that naming strategies were introduced in Json.NET 9.0.1 so if you are using an earlier version you will need to create your own camel case name mapper such as the one shown in this answer.

like image 115
dbc Avatar answered Oct 18 '22 18:10

dbc