Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "inline" a property in resulting json with json.net

I have a property in one of my classes that I'm trying to serialize with json.net that I would like to "inline", meaning, I don't want to have the property be nested into an element with the property name, but its content be directly within its parent.

Here's an example, let say I have the following class structure:

public interface ISteeringWheelIdentifier {}

public interface ISteeringWheel 
{
    ISteeringWheelIdentifier Identifier {get;}
}

public class ManufacturerIdentifier : ISteeringWheelIdentifier
{
    public string ManufacturerEmail {get; set;}
}

public class PartNumberIdentifier : ISteeringWheelIdentifier
{
    public string PartNumber {get; set;}
}

public class ClassicSteeringWheel : ISteeringWheel 
{
    public ClassicSteeringWheel(ManufacturerIdentifier identifier)
    {
        Identifier = identifier;
    }

    public string HornButtonManufacturer {get; set;}

    public ISteeringWheelIdentifier Identifier {get;private set;}
}

public class ModernSteeringWheel : ISteeringWheel
{
    public ModernSteeringWheel(PartNumberIdentifier identifier)
    {
        Identifier = identifier;
    }

    public string TouchpadManufacturer {get; set;}

    public ISteeringWheelIdentifier Identifier {get;private set;}
}

public class Car 
{
    public string CarBrand {get; set;}
    public ISteeringWheel SteeringWheel {get; set;}

}

If I try to serialize two test objects using the following code:

public static void Main()
{
    var car1 = new Car{CarBrand="Ford", SteeringWheel = new ModernSteeringWheel(new PartNumberIdentifier{PartNumber = "123456"})};
    var json = JsonConvert.SerializeObject(car1, Formatting.Indented);
    Console.WriteLine(json);

    var car2 = new Car{CarBrand="Toyota", SteeringWheel = new ClassicSteeringWheel(new ManufacturerIdentifier{ManufacturerEmail = "[email protected]"})};
    json = JsonConvert.SerializeObject(car2, Formatting.Indented);
    Console.WriteLine(json);
}

You get this result:

{
  "CarBrand": "Ford",
  "SteeringWheel": {
    "TouchpadManufacturer": null,
    "Identifier": {
      "PartNumber": "123456"
    }
  }
}
{
  "CarBrand": "Toyota",
  "SteeringWheel": {
    "HornButtonManufacturer": null,
    "Identifier": {
      "ManufacturerEmail": "[email protected]"
    }
  }
}

But, for my case, the Identifier is just a way to manage the different ways that steering wheels can be identified, and I don't need to have that property. The resulting Json I'm expecting would be the following:

{
  "CarBrand": "Ford",
  "SteeringWheel": {
    "TouchpadManufacturer": null
    "PartNumber": "123456"
  }
}
{
  "CarBrand": "Toyota",
  "SteeringWheel": {
    "HornButtonManufacturer": null,
    "ManufacturerEmail": "[email protected]"
  }
}

Obviously, I could do it by having both ManufacturerEmail and PartNumber in the ISteeringWheel and putting one or the other null and setting to ignore null values, but I'd rather keep things correctly separated in my classes.

I've created a fiddle with the working above code here: https://dotnetfiddle.net/C9RPy9

like image 234
Gimly Avatar asked Nov 09 '22 05:11

Gimly


1 Answers

One approach is to create your own custom json deserializer for ISteeringWheelIdentifier, in which you should implement the desired deserialization result for each steering wheel identifier type (see http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm) for an example. Then you should set an JsonConverter attribute for your Identifier property [JsonConverter(typeof([Name of your new converter]))] and it will be deserialized however you specified.

EDIT - when actually implementing it, turns out it's a bit tricker to get the desired behavior. The converter you need to create is one for the ISteeringWheel interface. In it, go over all the properties until you get to the identifier property, and handle its serialization. To the example:

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JObject jo = new JObject();
        Type type = value.GetType();

        foreach (var prop in type.GetProperties())
        {
            if (prop.CanRead)
            {
                var propVal = prop.GetValue(value, null);
                if (prop.Name == "Identifier")
                {
                    // Iterate over all properties of the identifier, but don't add the identifier object itself 
                    // to the serialized result.
                    Type identifierType = propVal.GetType();
                    foreach (var identifierProp in identifierType.GetProperties())
                    {
                        var identifierPropVal = identifierProp.GetValue(propVal, null);
                        jo.Add(identifierProp.Name, identifierPropVal != null ? JToken.FromObject(identifierPropVal, serializer) : null);
                    }
                }
                else
                {
                    // Add the property to the serialized result
                    jo.Add(prop.Name, propVal != null ? JToken.FromObject(propVal, serializer) : null);
                }
            }
        }

        jo.WriteTo(writer);
    }

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

    public override bool CanRead
    {
        get { return false; }
    }
}

Now all that remains is to add the attribute to the car class:

public class Car
{
    public string CarBrand { get; set; }

    [JsonConverter(typeof(SteeringWheelJsonConverter))]
    public ISteeringWheel SteeringWheel { get; set; }

}
like image 90
PartlyCloudy Avatar answered Nov 14 '22 22:11

PartlyCloudy