I am writing a Cmdlet and need to pass object structures into an API client that may contain PSObject
s. 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 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;
}
}
Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver
. Is there a way to make the converter respect that?
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.
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.
Contains methods for serializing Apex objects into JSON format and deserializing JSON content that was serialized using the serialize method in this class.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With