0

I've written a custom JsonConverter which if it fails then attempts the JSON parse using the default behaviour. The code is below and it's working fine, inspired by this answer to another question.

Basically what I do is after my own attempt at conversion fails, I set the Converter property of the serialiser for my type to null, call the deserialisation (which seems to result in it skipping my converter, which is good) and then in my finally clause I restore it (I do this in the finally clause so the Converter will be restored even if an exception is called).

My questions are:

  1. Does the serialiser passed by JSON.Net to this function have a shared "ContractResolver" or it's own unique copy?
  2. If it has it's own unique copy, I presume the finally clause here is unnecessary yes?
  3. If it's "ContractResolver" is shared, I presume what I'm doing is horrifically non-thread safe, as any deserialisation from a separate thread which occurs at the same time may skip the custom serialiser if another deserialisation just happens to be going ahead at the same time (I presume I'll have to fix this with locks)?
  4. Is there a better totally different way to fix this?
public override object ReadJson (
    JsonReader reader,
    Type objectType,
    object existingValue,
    JsonSerializer serializer) 
    {
        JToken nextToken = JToken.ReadFrom (reader);
        try {
            using (JTokenReader reader1 = new JTokenReader (nextToken))
            {
                // Attempt to parse in custom fashion
            }
        }
        catch (Exception e)
        {
            // If the previous attempt threw, fallback behaviour here:
            using (JTokenReader reader2 = new JTokenReader(nextToken))
            {
                JsonConverter originalConverter = null;
                JsonContract contract = serializer.ContractResolver.ResolveContract(objectType);
                try
                {
                    originalConverter = contract.Converter;
                    contract.Converter = null;
                    return serializer.Deserialize(reader2, objectType);
                }
                finally
                {
                    contract.Converter = originalConverter;
                }
            }
        }        
    }
}

Edit:

Using a completely new serializer for the inner loop seems to also work and presumably avoids threading issues. Is this a reasonable approach?

using (JTokenReader reader2 = new JTokenReader(nextToken))
{
    JsonSerializer tempSerializer = JsonSerializer.Create(new JsonSerializerSettings());
    tempSerializer.ContractResolver.ResolveContract(objectType).Converter = null;
    return tempSerializer.Deserialize(reader2, objectType);
}
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 1
    1, 3) The contract resolver is shared globally, see [Does Json.NET cache types' serialization information?](https://stackoverflow.com/q/33557737/3744182). 2) Even with a local contract resolver the `finally` is needed in case there are multiple non-nested instances of the type in the object graph being deserialized. 4) see [JSON.Net throws StackOverflowException when using `[JsonConvert()]`](https://stackoverflow.com/q/29719509/3744182) and/or [Json.NET custom serialization with JsonConverter - how to get the “default” behavior](https://stackoverflow.com/q/35586987/3744182). – dbc Mar 29 '19 at 03:03
  • In fact this may be a duplicate of those three, or be distinct enough to warrant an answer. Do you have a preference? – dbc Mar 29 '19 at 03:03
  • @dbc: I think it might be somewhat distinct. See my edit. Is that a better approach? – Clinton Mar 29 '19 at 03:15
  • Unfortunately the default contract resolver is still shared globally, see [Does Json.NET cache types' serialization information?](https://stackoverflow.com/q/33557737/3744182), so that will still cause problems. And if you allocate a local contract resolver for every call the performance will suffer. Disabling the converter using a `[ThreadStatic]` or using `serializer.Populate()` should work instead. – dbc Mar 29 '19 at 03:18
  • @dbc I can't see how `[ThreadStatic]` helps here, as I have to change the converter (i.e. set `serializer.ContractResolver.ResolveContract(objectType).Converter = null`) and that's going to be seen across all threads. I presume I'm missing something yes? – Clinton Mar 29 '19 at 03:25
  • Yes, you just need to make [`CanRead`](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonConverter_CanRead.htm) and [`CanWrite`](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonConverter_CanWrite.htm) return `false` when some thread static field is temporarily set. See [JSON.Net throws StackOverflowException when using `[JsonConvert()]`](https://stackoverflow.com/q/29719509/3744182) for an example of such. – dbc Mar 29 '19 at 03:30
  • Oh, so the JsonConverter will fallback to default behaviour if on entry `CanRead`/`CanWrite` is false? – Clinton Mar 29 '19 at 03:32
  • Yes, see [How to use default serialization in a custom JsonConverter](https://stackoverflow.com/q/29616596/3744182). So, make an answer that pulls together all those other answers? – dbc Mar 29 '19 at 03:56

0 Answers0