Run default serialization logic from JsonConverter
Asked Answered
D

3

39

I have a JsonConverter that, depending on an instance specific flag, needs to either

  • run custom serialization logic
  • run the default Json.NET serialization logic

How can the default Json.NET serialization logic be ran from a JsonConverter?

Thanks

Durer answered 26/1, 2014 at 16:51 Comment(1)
possible duplicate of Recursively call JsonSerializer in a JsonConverterBaghdad
M
16

Here is an example. Say your class to serialize looks like this:

class Foo
{
    public bool IsSpecial { get; set; }
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
}

The IsSpecial flag is used to control whether we do something special in the converter or just let things serialize naturally. You can write your converter like this:

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Foo foo = (Foo)value;
        JObject jo;
        if (foo.IsSpecial)
        {
            // special serialization logic based on instance-specific flag
            jo = new JObject();
            jo.Add("names", string.Join(", ", new string[] { foo.A, foo.B, foo.C }));
        }
        else
        {
            // normal serialization
            jo = JObject.FromObject(foo);
        }
        jo.WriteTo(writer);
    }

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

Then, to use the converter, pass an instance of it to the SerializeObject method (e.g. in the settings). (Do NOT decorate the target class with a JsonConverter attribute, or this will result in an infinite recursive loop when you serialize.)

class Program
{
    static void Main(string[] args)
    {
        List<Foo> foos = new List<Foo>
        {
            new Foo
            {
                A = "Moe",
                B = "Larry",
                C = "Curly",
                IsSpecial = false
            },
            new Foo
            {
                A = "Huey",
                B = "Dewey",
                C = "Louie",
                IsSpecial = true
            },
        };

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new FooConverter());
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(foos, settings);
        Console.WriteLine(json);
    }
}

Output:

[
  {
    "IsSpecial": false,
    "A": "Moe",
    "B": "Larry",
    "C": "Curly"
  },
  {
    "names": "Huey, Dewey, Louie"
  }
]
Mogerly answered 27/1, 2014 at 3:33 Comment(4)
This works well, except that I have custom converters that need to be included in the "normal" serialization process. So for this example, FooConverter also needs to be included in the serialization process. This then causes the "normal" serialization to go through the FooConverter again, with IsSpecial = false. Stated differently, the problem was to serialize a field encountered during serialization differently than an object passed directly into JsonConvert.Serialize. A (non-optimal) solution is to require that all field references be annotated with a [JsonConverter] reference.Durer
I think I may have misunderstood your question then. Can you edit your question to include a simple example (Foo, Bar, etc.) of the class hierarchy you are trying to serialize and what your desired JSON output would be if your converter worked the way you wanted? It sounds like you want it to behave differently depending on whether the object to be serialized is at the root level or not, as opposed to being controlled by a flag on the object instances.Mogerly
This works terrifically as long as you are not trying to preserve references. It does not appear to work if you need the id field, however.Medeiros
Custom converters, or custom serializer settings can be accomplished by passing in a new JsonSerializer into JObject.FromObject. For example: var jo = JObject.FromObject(value, JsonSerializer.CreateDefault(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }));Ocieock
C
8

You can change the CanWrite property to disable a custom serializer. This won't work right if the object can contain children of the same type or if you are serializing in more than one thread.

class FooConverter : JsonConverter
{
    bool _canWrite = true;
    public override bool CanWrite
    {
        get { return _canWrite;}
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Foo foo = (Foo)value;
        JObject jo;
        if (foo.IsSpecial)
        {
            // special serialization logic based on instance-specific flag
            jo = new JObject();
            jo.Add("names", string.Join(", ", new string[] { foo.A, foo.B, foo.C }));
        }
        else
        {
            // normal serialization
            _canWrite = false;
            jo = JObject.FromObject(foo);
            _canWrite = true;
        }
        jo.WriteTo(writer);
    }
}
Coreen answered 28/5, 2015 at 0:57 Comment(1)
+1 -- I have a custom contract resolver that creates a hidden json converter -- each one is it's own instance, so modifying the instance level CanRead and CanWrite to prevent recursion is working perfectly for me. -- Though, I'm using serializer.Serialize(writer, value); to write the value, and I recommend setting the value back to the original value in a finally block so that it never gets stuck in a weird state.Sorghum
A
0

I have used the following Newtonsoft.Json converter to partially customize deserialization. My implementation demonstrates the following:

  • Avoid changing serialization (write) behavior.
  • Change instantiation behavior.
  • Avoid changing further deserialization behavior, i.e. populate normally, honoring all existing customizations.

This example can be adapted to conditionally customize serialization behavior. Another answer on StackOverflow used a [ThreadStatic] static bool IsEnabled along with override bool CanWrite => IsEnabled. The WriteJson method would temporarily disable the converter, recurse using serializer.Serialize(writer, value), and in a finally block re-enable the converter. That way, the converter can choose to skip affecting the serialization behavior.

public sealed class CustomJsonConverter<
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>
    : JsonConverter<T>
{
    public override bool CanWrite => false;
    public override bool CanRead => true;

    public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
    {
        throw new NotSupportedException("This type is only used for deserialization.");
    }

    public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return default;

        var result = (T)RuntimeHelpers.GetUninitializedObject(typeof(T));
        serializer.Populate(reader, result);
        return result;
    }
}
Ancy answered 6/1 at 19:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.