11

Is there any way to serialize a Json object property that varies from decimal to decimal[] in a single operation?

In my Json product feed special offer items are represented as an array (normal price/ sale price). Normal items are just the price. Like so:

[
    {
        "product" : "umbrella",
        "price" : 10.50,
    },
        "product" : "chainsaw",
        "price" : [
                      39.99,
                      20.0
                    ]
    }
]

The only way I can get it to work is if I make the property an object like so:

public class Product
{
    public string product { get; set; }
    public object price { get; set; }
}

var productList = JsonConvert.DeserializeObject<List<Product>>(jsonArray);

But if I try to make it decimal[] then it will throw exception on a single decimal value. Making it an object means that the arrays values are a JArray so I have to do some clean up work afterwards and other mapping in my application requires the property type to be accurate so I have to map this to an unmapped property then initialize another property which is no big deal but a little messy with naming.

Is object the only option here or is there some magic I can do with the serializer that either adds single value to array or the second value to a separate property for special offer price?

Guerrilla
  • 13,375
  • 31
  • 109
  • 210

2 Answers2

23

You have to write a custom converter for that price property (because it's not in well format), and use like this:

 public class Product
    {
        public string product { get; set; }
        [JsonConverter(typeof(MyConverter ))]
        public decimal[] price { get; set; }
    }


 public class MyConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return false;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if(reader.TokenType == JsonToken.StartArray)
            {
                return serializer.Deserialize(reader, objectType);
            }
            return new decimal[] { decimal.Parse(reader.Value.ToString()) };              
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

And then parse as normal:

 var productList = JsonConvert.DeserializeObject<List<Product>>(jsonStr);
nhabuiduc
  • 998
  • 7
  • 8
  • Do you have to specify the attribute on the type if you're submitting `MyConverter()` as a parameter on deserialize method? – Aydin May 10 '15 at 03:46
  • Thanks! This works perfectly. I set a "decimal [] RawPrice" then "Price" and "SalePrice" access this with getter/setter and everything happens in one operation and my class maps nicely elsewhere :) – Guerrilla May 10 '15 at 15:20
4

Another way to do this is to add a Json field that is dynamic and marked it with JsonProperty attribute so it lines up w/ the actual Json. Then have the real field in .NET that will take that Json field and translate it into a real array like so...

using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var json = "{ \"price\": [ 10, 20 ] }";
        var json2 = "{ \"price\": 15 }";

        var foo1 = JsonConvert.DeserializeObject<Foo>(json);
        var foo2 = JsonConvert.DeserializeObject<Foo>(json2);

        foo1.Price.ForEach(Console.WriteLine);
        foo2.Price.ForEach(Console.WriteLine);
    }
}

public class Foo {
    [JsonProperty(PropertyName = "price")]
    public dynamic priceJson { get; set; }

    private List<int> _price;

    public List<int> Price {
        get {
            if (_price == null) _price = new List<int>();

            _price.Clear();

            if (priceJson is Newtonsoft.Json.Linq.JArray) {

                foreach(var price in priceJson) {
                    _price.Add((int)price);
                }
            } else {
                _price.Add((int)priceJson);
            }

            return _price;
        }
    }
}

Live sample at: https://dotnetfiddle.net/ayZBlL

Jimmy Chandra
  • 6,472
  • 4
  • 26
  • 38