0

I have some class with following structure

public class A {
  [JsonProperty(PropertyName = "prop_b")]
  public B PropB {get; set;}
}

public class B {
  [JsonProperty(PropertyName = "val1")]
  public int Val1 {get; set;}

  [JsonProperty(PropertyName = "val2")]
  public int Val2 {get; set;}
}

Which would be serialized to a JSON of the following scheme:

{
  "prop_b": { "val1": X, "val2": Y }
}

Is there any way for me to skip B and serialize the fields directly, without changing the class structure? I would assume there would be some attribute, or that I could implement one.

{
   "val1": X,
   "val2": Y
}
4
  • 1
    Can you pass the PropB property of your A class directly to your Serialize method? Commented Sep 3, 2017 at 5:56
  • I could, but that's not a generic enough solution, especially if A has more than one field Commented Sep 3, 2017 at 5:57
  • You might need a custom solution. Maybe this could help: stackoverflow.com/questions/11934487/… Commented Sep 3, 2017 at 6:00
  • Can you use reflection and LINQ to JSON in your project? newtonsoft.com/json/help/html/LINQtoJSON.htm Commented Sep 3, 2017 at 7:53

1 Answer 1

2

One possibility could be to create a JsonConverter.

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
  public sealed class ImportChildAttribute : Attribute
  {
  }

  class ImportChildJsonConverter : JsonConverter
  {
    public override bool CanConvert(Type objectType)
    {
      var attr = CustomAttributeExtensions.GetCustomAttribute(objectType.GetTypeInfo(), typeof(ImportChildAttribute), true);
      if (attr != null)
      {
        var props = objectType.GetProperties();
        if (props.Length != 1)
          throw new NotSupportedException($"Only supports {nameof(ImportChildAttribute)} on classes with one property.");
        return true;
      }

      return false;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
      // Deserialize the object into the first property.
      var props = objectType.GetProperties();
      var obj = Activator.CreateInstance(objectType);
      var val = serializer.Deserialize(reader, props[0].PropertyType);
      props[0].SetValue(obj, val);
      return obj;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
      // Find the only property and serialize it.
      var props = value.GetType().GetProperties();
      serializer.Serialize(writer, props[0].GetValue(value));
    }
  }

Then you can put the ImportChild attribute on all the classes you want to have this behaviour.

  [ImportChild]
  public class A
  {
    [JsonProperty(PropertyName = "prop_b")]
    public B PropB { get; set; }
  }

  public class B
  {
    [JsonProperty(PropertyName = "val1")]
    public int Val1 { get; set; }

    [JsonProperty(PropertyName = "val2")]
    public int Val2 { get; set; }
  }

Finally, try it out:

  var settings = new JsonSerializerSettings
  {
    Converters = new[] { new ImportChildJsonConverter() },
    Formatting = Formatting.Indented
  };

  var obj = new A { PropB = new B { Val1 = 1, Val2 = 2 } };
  string json = JsonConvert.SerializeObject(obj, settings);
  Console.WriteLine(json);
  /* Outputs:
     {
       "val1": 1,
       "val2": 2
     }
  */

  var originalObj = JsonConvert.DeserializeObject<A>(json, settings);
  // originalObj and obj are now identical.
Sign up to request clarification or add additional context in comments.

6 Comments

Rather than on the type itself, I'd put the [ImportChild] attribute on the property to be serialized. That way other get-only properties won't interfere.
@dbc But JsonConverters don't work on properties - they work on types. So, to my knowledge, it is not possible to detect whether the property being serialized has a specific attribute. If you know how to do it, please post another answer with that solution. I'd love to see it!
You could put the converter explicitly on the type, with [JsonConverter(typeof(ImportChildJsonConverter))]. That's going to improve performance by avoiding calls to CanConvert for every type including primitive types. Or, you could do it with two attributes: an [ImportChild] attribute and an [ImportChildProperty] attribute.
Your other approach could work, but the code will get more complicated since there might be name clashes. I just provided a simple example that shows it can be done - it can of course be improved a lot as you suggest, but if we added those complexities to the code, it may be more difficult for the original poster to understand the answer.
I meand, put the converter on A like so: [JsonConverter(typeof(ImportChildConverter))] public class A { [ImportChild] public B PropB { get; set; } }. But, I agree the simplest answer is often best, and the current works and is simple.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.