How to “inline” a property in resulting json with json.net

浪子不回头ぞ 提交于 2019-12-23 12:45:06

问题


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 = "test@demo.com"})};
    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": "test@demo.com"
    }
  }
}

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": "test@demo.com"
  }
}

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


回答1:


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; }

}


来源:https://stackoverflow.com/questions/40862660/how-to-inline-a-property-in-resulting-json-with-json-net

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!