Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing Multiple Values into a Single Property

Tags:

json

json.net

I have a JSON response that includes a few flags:

{"df":1,"dr":0,"pf":0,"pr":0,"ft":0,"rt":1}

I want to deserialize this into a flags enumeration:

public class Foo {
    public Doors Doors { get; set; }
    public Trunk Trunks { get; set; }
}

df, dr, pf, pr should deserialize into Doors flags enum, while ft, rt should go into Trunks. I'm expecting this to be custom code to make the decision. What I'm looking for is something that will allow me to say:

[JsonProperty("df,dr,pf,pr"), JsonConverter(typeof(DoorsConverter))]
public Doors Door { get; set; }

that would allow me to handle the construction of the property based on those values. Does this type of thing exist or am I stuck pushing the individual properties into non-public fields and constructing the property after the fact?

like image 555
Colin Bowern Avatar asked Feb 07 '26 17:02

Colin Bowern


1 Answers

I agree with @JeffMercado that the best way to handle this is by creating a custom converter that handles the entire class. You said in your comments that you got hung up with creating such a converter because you didn't want to write code to handle all the other class properties (not shown in your question) since they do not require any special handling. To that end, I present an idea for a converter which will serialize/deserialize your object using JSON.Net's usual mechanisms for all the standard properties, but then handle the Doors and Trunks enum fields specially. Since I don't know what your other properties are called, I just made up a few that were related to cars. I also renamed your Foo class to Car in keeping with that theme.

Here are the class and enum definitions for this example:

public class Car
{
    [JsonIgnore]
    public Door Doors { get; set; }
    [JsonIgnore]
    public Trunk Trunks { get; set; }
    [JsonProperty("make")]
    public string Make { get; set; }
    [JsonProperty("model")]
    public string Model { get; set; }
    [JsonProperty("year")]
    public int Year { get; set; }
    [JsonProperty("MSRP")]
    public decimal MSRP { get; set; }
}

[Flags]
public enum Door 
{
    None = 0,
    DriverSideFront = 1,
    DriverSideRear = 2,
    PassengerSideFront = 4,
    PassengerSideRear = 8 
}

[Flags]
public enum Trunk
{
    None = 0,
    Front = 1, 
    Rear = 2
}

And here is the code for the custom converter:

public class CarConverter : JsonConverter
{
    private static Dictionary<string, Door> doorMap;
    private static Dictionary<string, Trunk> trunkMap;

    static CarConverter()
    {
        doorMap = new Dictionary<string, Door>();
        doorMap.Add("df", Door.DriverSideFront);
        doorMap.Add("dr", Door.DriverSideRear);
        doorMap.Add("pf", Door.PassengerSideFront);
        doorMap.Add("pr", Door.PassengerSideRear);

        trunkMap = new Dictionary<string, Trunk>();
        trunkMap.Add("ft", Trunk.Front);
        trunkMap.Add("rt", Trunk.Rear);
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Car));
    }

    public override object ReadJson(JsonReader reader, Type objectType, 
                                    object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        Car car = jo.ToObject<Car>();
        car.Doors = GetDoors(jo);
        car.Trunks = GetTrunks(jo);
        return car;
    }

    private Door GetDoors(JObject obj)
    {
        Door doors = Door.None;
        foreach (KeyValuePair<string, Door> kvp in doorMap)
        {
            if (obj[kvp.Key].Value<int>() == 1)
            {
                doors |= kvp.Value;
            }
        }
        return doors;
    }

    private Trunk GetTrunks(JObject obj)
    {
        Trunk trunks = Trunk.None;
        foreach (KeyValuePair<string, Trunk> kvp in trunkMap)
        {
            if (obj[kvp.Key].Value<int>() == 1)
            {
                trunks |= kvp.Value;
            }
        }
        return trunks;
    }

    public override void WriteJson(JsonWriter writer, object value, 
                                   JsonSerializer serializer)
    {
        Car car = (Car)value;
        JObject obj = JObject.FromObject(value);
        AddDoors(obj, car.Doors);
        AddTrunks(obj, car.Trunks);
        obj.WriteTo(writer);
    }

    private void AddDoors(JObject obj, Door doors)
    {
        foreach (KeyValuePair<string, Door> kvp in doorMap)
        {
            obj.Add(kvp.Key, ((doors & kvp.Value) != Door.None) ? 1 : 0);
        }
    }

    private void AddTrunks(JObject obj, Trunk trunk)
    {
        foreach (KeyValuePair<string, Trunk> kvp in trunkMap)
        {
            obj.Add(kvp.Key, ((trunk & kvp.Value) != Trunk.None) ? 1 : 0);
        }
    }
}

Finally, here is a test program showing usage:

static void Q18066528()
{
    string json = @"{""df"":1,""dr"":0,""pf"":0,""pr"":1,""ft"":0,""rt"":1,
       ""make"":""Chevrolet"",""model"":""Corvette"",""year"":2013,""MSRP"":49600}";

    JsonSerializerSettings settings = new JsonSerializerSettings 
    { 
        Converters = new List<JsonConverter> { new CarConverter() } 
    };

    Car car = JsonConvert.DeserializeObject<Car>(json, settings);

    Console.WriteLine("Model: " + car.Model);
    Console.WriteLine("Year: " + car.Year);
    Console.WriteLine("Doors: " + car.Doors);
    Console.WriteLine("Trunks: " + car.Trunks);

    string json2 = JsonConvert.SerializeObject(car, settings);

    Console.WriteLine(json2);
}

Output of the above:

Model: Corvette
Year: 2013
Doors: DriverSideFront, PassengerSideRear
Trunks: Rear
{"make":"Chevrolet","model":"Corvette","year":2013,"MSRP":49600.0,"df":1,"dr":0,
"pf":0,"pr":1,"ft":0,"rt":1}

Important note: You might be tempted to decorate the Car class with [JsonConverter(typeof(CarConverter))], but don't do it. If you do, then the CarConverter won't work correctly: the converter will end up calling itself recursively until it errors out with a StackOverflowException. (A converter that handles all the class properties manually would not have this problem.)

like image 123
Brian Rogers Avatar answered Feb 09 '26 11:02

Brian Rogers