0

Sourcecode to showcase the issue: https://github.com/Snuffsis/ConverterExample

So I have an issue that is exactly the same as in this stackoverflow question:

C# Newtonsoft.Json Custom Deserializer

And while that answer does help for properties that are simple types (int, bool, string etc) it doesn't work when there needs to be a nested object. As it throws an exception for Newtonsoft.Json.JsonSerializationException: Self referecing loop detected for property 'Value' with type ... where the type is the json object, in this case soBillingContact.

It should be able to handle these two JSON formats
Example:

{
  "printNoteOnInternalDocuments": {
    "value": true
  },
  "soBillingContact": {
    "value": {
      "overrideContact": {
        "value": true
      },
      "name": {
        "value": "string"
      },
      "attention": {
        "value": "string"
      },
      "email": {
        "value": "string"
      },
      "web": {
        "value": "string"
      },
      "phone1": {
        "value": "string"
      },
      "phone2": {
        "value": "string"
      },
      "fax": {
        "value": "string"
      }
    }
  }
}
{
  "printNoteOnInternalDocuments": true,
  "soBillingContact": {
    "overrideContact": true,
    "contactId": 0,
    "name": "string",
    "attention": "string",
    "email": "string",
    "web": "string",
    "phone1": "string",
    "phone2": "string",
    "fax": "string"
  }
}

The solution in the linked question works fine for the object itself if i create the object as a root. It's only when it's a nested object that it becomes a problem.

I am trying to avoid having to write a custom converter for each json object that exists, and instead try to make a generic one. Which is probably my issue and maybe should be abandoned. But just checking if anyone might have any ideas for a solution.

And aside from that solution above, I have written my own converters that does similar stuff which works fine. Along with custom converter for each specific nested objects, which also works fine.

This is the code that i made myself that works when its for a specific object: Main:

static void Main(string[] args)
{
  var vSalesOrder = new SalesOrder()
  {
    Project = 1,
    PrintDescriptionOnInvoice = true,
    PrintNoteOnExternalDocuments = true,
    SoBillingContact = new Contact
    {
      Attention = "attention",
      Email = "@whatever.se",
      Fax = "lolfax"
    }
  };

  var jsonString = JsonConvert.SerializeObject(vSalesOrder);
}

Expected output after this should have similar structure as the json above, except for the few properties that have been left out.

SalesOrder Class:
WrapWithValueConverter code can be found in the linked overflow question at the top.

public class SalesOrder
{
  [JsonProperty("project", NullValueHandling = NullValueHandling.Ignore)]
  [JsonConverter(typeof(WrapWithValueConverter<int?>))]
  public int? Project { get; set; }
  
  [JsonProperty("printDescriptionOnInvoice", NullValueHandling = NullValueHandling.Ignore)]
  [JsonConverter(typeof(WrapWithValueConverter<bool>))]
  public bool PrintDescriptionOnInvoice { get; set; }

  [JsonProperty("printNoteOnExternalDocuments", NullValueHandling = NullValueHandling.Ignore)]
  [JsonConverter(typeof(WrapWithValueConverter<bool>))]
  public bool PrintNoteOnExternalDocuments { get; set; }

  [JsonProperty("printNoteOnInternalDocuments", NullValueHandling = NullValueHandling.Ignore)]
  [JsonConverter(typeof(WrapWithValueConverter<bool>))]
  public bool PrintNoteOnInternalDocuments { get; set; }

  [JsonProperty("soBillingContact", NullValueHandling = NullValueHandling.Ignore)]
  [JsonConverter(typeof(ContactDtoJsonConverter))]
  public Contact SoBillingContact { get; set; }
}

ContactDtoJsonConverter Class:

public class ContactDtoJsonConverter : JsonConverter<Contact>
{
    public override bool CanRead => false;

    public override bool CanWrite => true;

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

    public override void WriteJson(JsonWriter writer, Contact value, JsonSerializer serializer)
    {
        var dtoContact = new DtoContact
        {
            Value = value
        };
        JToken t = JToken.FromObject(dtoContact);
        JObject o = (JObject)t;

        o.WriteTo(writer);
    }
}

DtoContact Class:

public class DtoContact
{
  [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
  public Contact Value { get; set; }
}

Contact Class:

public class Contact
{
   [JsonProperty("overrideContact", NullValueHandling = NullValueHandling.Ignore)]
   public bool OverrideContact { get;set; }

   [JsonProperty("attention", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Attention { get; set; }
  
   [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Email { get; set; }
  
   [JsonProperty("fax", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Fax { get; set; }
  
   [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Name { get; set; }
  
   [JsonProperty("phone1", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Phone1 { get; set; }
  
   [JsonProperty("phone2", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Phone2 { get; set; }
  
   [JsonProperty("web", NullValueHandling = NullValueHandling.Ignore)]
   [JsonConverter(typeof(StringDtoJsonConverter))]
   public string Web { get; set; }
}

StringDtoJsonConverter Class:

public class StringDtoJsonConverter : JsonConverter<string>
{
  public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer)
  {
    return (string)reader.Value;
  }
  
  public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer)
  {
    JToken t = JToken.FromObject(value);
    if (t.Type != JTokenType.Object)
    {
      var dtoValue = new DtoString
      {
        Value = value
      };
      serializer.Serialize(writer, dtoValue);
    }
  }
}
17
  • 1
    Please share with us your custom converter's code as well Commented Nov 30, 2022 at 8:23
  • And the class definition too. As direct translation to thing like public class Web{public string Value { get; set; }}, you may want to exclude them from possible answers. Commented Nov 30, 2022 at 8:34
  • @PeterCsala The custom converter code that I am trying is exactly the same as the one in the previously linked stack overflow question. The custom converter code that I made that worked looks like this: public override void WriteJson(JsonWriter writer, Contact value, JsonSerializer serializer) { var dtoContact = new DtoContact { Value = value }; JToken t = JToken.FromObject(dtoContact); JObject o = (JObject)t; o.WriteTo(writer); } } Commented Nov 30, 2022 at 9:02
  • Could you please share with us the definition of the Contact class? Commented Nov 30, 2022 at 9:08
  • 1
    Ah of course. You can just remove that constructor. Its not necessary Commented Nov 30, 2022 at 11:37

2 Answers 2

2

The converter from this answer to C# Newtonsoft.Json Custom Deserializer can be fixed to avoid the Self referecing loop error by applying [JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)] to DTO.value like so:

sealed class DTO { [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)] public TValue value { get; set; } public object GetValue() => value; }

Note that, by doing this, PreserveReferencesHandling will be disabled.

That being said, you wrote It should be able to handle these two JSON formats. If you know in advance which format you require, the easiest way to handle both would be to create a custom contract resolver that applies the converter in runtime. Doing so has the added advantage of allowing you to remove all the [JsonConverter(typeof(WrapWithValueConverter<T>))] attributes from your model.

First define the following contract resolver:

public class WrapWithValueContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property.Converter == null && property.ItemConverter == null) // property.Converter check is required to avoid applying the converter to WrapWithValueConverter<TValue>.DTO.value
            property.Converter = (JsonConverter)Activator.CreateInstance(typeof(WrapWithValueConverter<>).MakeGenericType(property.PropertyType));
        return property;
    }
}

public class WrapWithValueConverter<TValue> : JsonConverter
{
    // Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
    sealed class DTO { [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling  = ReferenceLoopHandling.Serialize)] public TValue value { get; set; } public object GetValue() => value; }

    public override bool CanConvert(Type objectType) => typeof(TValue).IsAssignableFrom(objectType);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        => serializer.Serialize(writer, new DTO { value = (TValue)value });

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        => serializer.Deserialize<DTO>(reader)?.GetValue();
}

public class NoConverter : JsonConverter
{
    // NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
    // By https://stackoverflow.com/users/3744182/dbc
    // To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
    public override bool CanConvert(Type objectType)  { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
    public override bool CanRead => false;
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Now, if you want the wrapped format, serialize and deserialize using the following settings:

DefaultContractResolver resolver = new WrapWithValueContractResolver // Cache statically and reuse for best performance
{
    //NamingStrategy = new CamelCaseNamingStrategy(), // Uncomment if you need camel case
}; 

var json = JsonConvert.SerializeObject(vSalesOrder, Formatting.Indented, settings);

var order2 = JsonConvert.DeserializeObject<SalesOrder>(json, settings);

If you don't want the wrapped format, serialize and deserialize without setting a resolver as usual.

Demo fiddle here.

Sign up to request clarification or add additional context in comments.

Comments

0

you can try this code,that doesn't need any custom converters

    var jsonObj = JObject.Parse(json);

    SalesOrder salesOrder = null;
    
    if (jsonObj["printNoteOnInternalDocuments"].Type == JTokenType.Boolean) 
                                                salesOrder = jsonObj.ToObject<SalesOrder>();
else
{
  var newJsonObj = new JObject
  {
  ["printNoteOnInternalDocuments"] = jsonObj["printNoteOnInternalDocuments"]["value"],

  ["soBillingContact"] = new JObject(  ((JObject) jsonObj["soBillingContact"]["value"]).Properties()
                .Select(p=> new JProperty( p.Name,p.Value["value"])))
  };

        salesOrder = newJsonObj.ToObject<SalesOrder>();
}

3 Comments

That is a solution that would be specifically for just the example. I made a repository with examples of the code above, with both working (with specific converters) and non-working (generic converter) examples. github.com/Snuffsis/ConverterExample Hopefully that helps with understanding what the problem is.
@Snuffsis You have classes, so you should know what json you have to deserialize. So you will have to customize your code for each case too. My post is just a hint. It can be easily made generic, I am just not a free coder to do this for you.
I should have been clearer I guess. The issue I needed help with wasn't really the conversion itself. It was for the exception I was getting.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.