I have a complex object graph that I am serializing/deserializing with Json.NET. Some of the objects derive from an abstract class, so in order for the deserialization to work properly, I needed to create a custom JsonConverter. Its only role is to select the appropriate concrete implementation of the abstract class at deserialization-time and allow Json.NET to continue on its way.
My problem comes when I want to serialize. I don't need to do anything custom at all. I want to get exactly the same behavior as I would get using JsonConvert.SerializeObject with no custom JsonConverter.
However, since I'm using the custom JsonConverter class for my deserialization needs, I'm forced to supply a WriteJson implementation. Since WriteJson is abstract, I can't just call base.WriteJson, but I want to do essentially that. So my question is, what do I put in that method to get the plain-Jane, default behavior? In other words:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// What goes here to get default processing?
}
In your custom JsonConverter, override CanWrite and return false:
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
Then you can just throw an exception from WriteJson, since it won't get called.
(Similarly, to get default behavior during deserialization, override CanRead and return false.)
Note that the same approach can be used for JsonConverter<T> (introduced in Json.NET 11.0.1) since it is just a subclass of JsonConverter that introduces type-safe versions of ReadJson() and WriteJson().
Related
I'm using a JsonConverter to deal with a polymorphic collection:
class ItemBatch
{
List<ItemBase> Items { get; set; }
}
// For type discrimination of ItemBase
class ItemTypes
{
public int Value { get; set; }
}
[JsonConverter(typeof(ItemConverter))]
abstract class ItemBase
{
public abstract ItemTypes Type { get; set; }
}
class WideItem : ItemBase
{
public override ItemTypes Type => 1;
public decimal Width { get; set; }
}
class HighItem : ItemBase
{
public override ItemTypes Type => 2;
public decimal Height { get; set; }
}
class ItemConverter : JsonConverter<ItemBase>
{
public override ItemBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader));
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
// deserialize depending on type.
}
}
Am using Blazor and store the ItemBatch in IndexedDb before retrieving again later and sending to WebAPI.
Serializing to IndexedDb and deserializing from IndexedDb works fine.
But, when I try to send the ItemBatch to a WebAPI, I get the error:
Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in
System.Text.Json.dll An exception of type
'System.Collections.Generic.KeyNotFoundException' occurred in
System.Text.Json.dll but was not handled in user code The given key
was not present in the dictionary.
From peeking at various values, I suspected an issue with case-sensitivity. Indeed, if I change:
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
to
int type;
try
{
type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
}
catch (Exception)
{
type = jsonDoc.RootElement.GetProperty("type").GetProperty("value").GetInt32();
}
then I get past this error and my WebAPI gets called.
What am I missing that allows me to serialize / deserialize to / from IndexedDb, but the WebApi Json conversion is having issues with case sensitivity.
Newtonsoft was case insensitive.
With System.Text.Json you have to pull some more levers.
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-6-0#case-insensitive-deserialization
Case-insensitive deserialization During deserialization,
Newtonsoft.Json does case-insensitive property name matching by
default. The System.Text.Json default is case-sensitive, which gives
better performance since it's doing an exact match. For information
about how to do case-insensitive matching, see Case-insensitive
property matching.
See this below URL as well:
https://makolyte.com/csharp-case-sensitivity-in-json-deserialization/
Here is a possible way to deal with it:
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-casing
above url is ::: How to enable case-insensitive property name matching with
System.Text.Json
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.getproperty?view=net-6.0#system-text-json-jsonelement-getproperty(system-string)
Remarks
Property name matching is performed as an ordinal, case-sensitive comparison.
I don't think you can overcome that behavior on the "roll your own".
But maybe you can chase this:
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.propertynamecaseinsensitive?view=net-6.0#system-text-json-jsonserializeroptions-propertynamecaseinsensitive
But that seems to be without "roll your own".
My conclusion is that using default serialization, the JsonSerializerOptions do not set a value "CamelCase" for the PropertyNamingPolicy, but the HttpClient and WebApi Request Pipeline do:
PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
This means that when I'm serializing and deserializing to IndexedDb, the default serialization leaves the Json as Pascal Case.
My custom JsonConverter, which was using the options parameter passed into the Read and Write methods, therefore used Pascal case on the client when working with IndexedDb.
However, when the same JsonConverter is called by the HttpClient and the WebApi request pipeline, the options are set to:
PropertyNamePolicy = JsonNamingPolicy.CamelCase.
and when trying to parse to a JsonDocument, the content is now camel cased and my reading of the JsonDocument using Pascal case assumptions then fell over.
The answer to my problem was to update the write method as follows:
public override void Write(Utf8JsonWriter writer, SaleCommandBase value, JsonSerializerOptions options)
{
JsonSerializerOptions newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };
JsonSerializer.Serialize(writer, (object)value, newOptions);
}
This forces the serialization to use Pascal case in all situations, whether that be local serialization on the client (when writing to IndexedDb) or whether serializing within the HttpClient when sending to a WebAPI.
Similary, in the read method:
using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader))
{
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };
return type switch
{
1 => jsonDoc.RootElement.Deserialize<WideItem>(newOptions),
2 => jsonDoc.RootElement.Deserialize<HighItem>(newOptions),
_ => throw new InvalidOperationException($"Cannot convert type '{type}'."),
};
}
By copying whatever the provided options are, but then overriding the naming policy to use Pascal case (PropertyNamingPolicy = null), then I can be assured that the Json document parsed will always be in Pascal case, regardless of the options provided by the framework.
When using JsonUtility to serialize in Unity, List of a class will be serialized as empty string if it's filled with subclasses of ExampleObjtype.
[Serializable]
public class SerializableGameEntityDebugSubclass : SerializableGameEntityDebug {
public SerializableGameEntityDebugSubclass() : base() {}
}
[Serializable]
public abstract class SerializableGameEntityDebug {
public string uuid = null;
public SerializableGameEntityDebug() {
this.uuid = "debuggin";
}
}
public class GameSaveData
{
public List<GameEntity.SerializableGameEntityDebugSubclass> serializableGameEntitiesDebug1 = new List<GameEntity.SerializableGameEntityDebugSubclass>{ new SerializableGameEntityDebugSubclass() };
public List<GameEntity.SerializableGameEntityDebug> serializableGameEntitiesDebug2 = new List<GameEntity.SerializableGameEntityDebug>{ new SerializableGameEntityDebugSubclass() };
}
serializableGameEntitiesDebug1 DOES get subclassed and serializableGameEntitiesDebug1 does NOT get subclassed. I find this very odd because even if I print out individually the serialized elements of the list, it works correctly in both cases.
There are two separate issues at play.
It seems JsonUtility won't serialize List of any abstract class no matter what. So the thing the list contains must not be an abstract class
When I change the abstract class to a regular class, it will serialize it, but it will only contain fields in the base class rather than child classes.
Therefore it seems the only workaround is to have many lists to serialize (one for each child class)
Update: A slightly more elegant solution was to switch from using JsonUtility to Json.net JsonConverter. This caused serialization to work perfectly, but not yet deserialization. I still had to write a converter class so the deserializer knows which class to instantiate. I followed this answer and it worked. Last but not least it seems that each serializable class needs to have a default empty constructor for the deserializer to call when trying to instantiate it before hydrating it, or else it might try to call other constructors with null args
I've defined and registered some custom marshallers for my domain objects. If used alone, just rendering one instance, works fine, but the problem comes when I return a map with an array of those instances. In this moment my custom marshaller is not beign invoked.
This is one of my marshallers:
class BackendCompanyMarshaller implements ObjectMarshaller<JSON> {
#Override
public boolean supports(Object object) {
object instanceof Company
}
#Override
public void marshalObject(Object object, JSON converter)
throws ConverterException {
JSONWriter writer = converter.getWriter()
writer.object()
writer.key('id').value(object.id)
.key('name').value(object.name?.encodeAsHTML()?:'')
.key('description').value(object.description?.encodeAsHTML()?:'')
.key('enterprise').value(object.enterprise?.encodeAsHTML()?:'')
writer.endObject()
}
}
Ans for example this is what I'm returning from my controller:
render text:[achievements:arrayOfAchievements, total:2] as JSON
In previous versions of grails I know there was deep marshallers but I haven't been able to find something similar for grails 3.
I have also tried to implement a custom marshaller for List, but I'm not sure what I should return or write.
I have a class that has more than a dozen properties. For most of the properties of primitive type, I hope to use the default BeanSerializer and BeanDeserializer or whatever to reduce the cumbersome code I need to write. For other properties of custom and array types, I want to do some custom serializer/deserializer. Note that I am not able to change the underlying JSON string. But I have full access to the android code. I am using Jackson 1.7.9/Ektorp 1.1.1.
shall I subclass BeanDeserializer? I am having trouble with that. It expects a default constructor with no parameters but I don't know how to call the super constructor.
class MyType{
// a dozen properties with primitive types String, Int, BigDecimal
public Stirng getName();
public void setName(String name);
// properties that require custom deserializer/serializer
public CustomType getCustom();
public void setCustom(CustomType ct);
}
class MyDeserializer extends BeanDeserialzer{
// an exception is throw if I don't have default constructor.
// But BeanDeserializer doesn't have a default constructor
// It has the below constructor that I don't know how to fill in the parameters
public MyDeserializer(AnnotatedClass forClass, JavaType type,
BeanProperty property, CreatorContainer creators,
BeanPropertyMap properties,
Map<String, SettableBeanProperty> backRefs,
HashSet<String> ignorableProps, boolean ignoreAllUnknown,
SettableAnyProperty anySetter) {
super(forClass, type, property, creators, properties, backRefs, ignorableProps,
ignoreAllUnknown, anySetter);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext dc, Object bean)
throws IOException, JsonProcessingException {
super.deserialize(jp, dc, bean);
MyType c = (MyType)bean;
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jp, JsonNode.class);
// Use tree model to construct custom
// Is it inefficient because it needs a second pass to the JSON string to construct the tree?
c.setCustom(custom);
return c;
}
}
I searched Google but couldn't find any helpful examples/tutorial. If anyone can send me some working examples that would be great! Thanks!
To sub-class BeanSerializer/-Deserializer, you would be better off using a more recent version of Jackson, since this area has been improved with explicit support via BeanSerializerModifier and BeanDeserializerModifier, which can alter configuration of instances.
But just to make sure, you can also specify custom serializer/deserializer to just be used on individual properties, like so:
class Foo {
#JsonSerialize(using=MySerializer.class)
public OddType getValue();
}
My application needs to combine extensive use of dependency injection with the use of JSON as a public API. This apparently leads to the need for a custom JavaScriptConverter.
Right now, my JavaScriptConverter's Deserialize method looks like this:
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var result = IocHelper.GetForType(type);
return result;
}
This hands back the appropriate class. Unfortunately, it fails to populate the class members with the applicable values. What I'm missing is a way to tell the Serializer, "Here's the type you asked for. Now fill it in."
The solution I used was to switch from JavaScriptSerializer to Newtonsoft's JSON converter
I was able to get a working round trip by writing a single CustomCreationConverter:
public class JsonDomainConverter : CustomCreationConverter<object>
{
public JsonDomainConverter()
{
}
public override bool CanConvert(Type objectType)
{
return objectType.IsInterface;
}
public override object Create(Type objectType)
{
return IocHelper.GetForType(objectType);
}
}
No doubt this same approach is possible with JavaScriptSerializer, I just couldn't figure out how to make it work. With the Newtonsoft stuff, it took a couple hours at the most, and just a couple lines of code.