Home > Back-end >  JSON Deserialization to existing library C# object
JSON Deserialization to existing library C# object

Time:10-28

I am getting web service responses in JSON, the strings like this:

{
 "adjusted": true,
 "queryCount": 2,
 "request_id": "6a7e466379af0a71039d60cc78e72282",
 "results": [
  {
   "c": 75.0875,
   "h": 75.15,
   "l": 73.7975,
   "n": 1,
   "o": 74.06,
   "t": 1577941200000,
   "v": 135647456,
   "vw": 74.6099
  },
  {
   "c": 74.3575,
   "h": 75.145,
   "l": 74.125,
   "n": 1,
   "o": 74.2875,
   "t": 1578027600000,
   "v": 146535512,
   "vw": 74.7026
  }
 ],
 "resultsCount": 2,
 "status": "OK",
 "ticker": "AAPL"
}

When I use my own C# objects (below) they look like this and I reserialize fine:

public class Aggregates
{
    [JsonProperty("ticker")]
    public string Ticker { get; set; }

    [JsonProperty("queryCount")]
    public int QueryCount { get; set; }

    [JsonProperty("resultsCount")]
    public int ResultsCount { get; set; }

    [JsonProperty("adjusted")]
    public bool Adjusted { get; set; }

    [JsonProperty("results")]
    public List<Aggregate> Aggregatelist { get; set; }

    [JsonProperty("status")]
    public string Status { get; set; }

    [JsonProperty("request_id")]
    public string RequestId { get; set; }

    [JsonProperty("count")]
    public int Count { get; set; }
}

   public class Aggregate
{
    /// <summary>
    /// The trading volume of the symbol in the given time period.
    /// </summary>
    [JsonProperty("v")]
    public object Volume { get; set; }

    /// <summary>
    /// The volume weighted average price.
    /// </summary>
    [JsonProperty("vw")]
    public double VolumeWeightedw { get; set; }

    /// <summary>
    /// The open price for the symbol in the given time period.
    /// </summary>
    [JsonProperty("o")]
    public double Open { get; set; }

    /// <summary>
    /// The close price for the symbol in the given time period.
    /// </summary>
    [JsonProperty("c")]
    public double Close { get; set; }

    /// <summary>
    /// The highest price for the symbol in the given time period.
    /// </summary>
    [JsonProperty("h")]
    public double High { get; set; }

    /// <summary>
    /// The lowest price for the symbol in the given time period.
    /// </summary>
    [JsonProperty("l")]
    public double Low { get; set; }

    /// <summary>
    /// The Unix Msec timestamp for the start of the aggregate window.
    /// </summary>
    [JsonProperty("t")]
    public long StartTime { get; set; }

    /// <summary>
    /// The number of transactions in the aggregate window.
    /// </summary>
    [JsonProperty("n")]
    public int TransactionsNum { get; set; }
}

However, I am forced now to use a library that has its own C# object Quote instead of my Aggregate object. Redundant to say that I cannot change Quote object.

public class Quote : IQuote
{
    public Quote();

    public DateTime Date { get; set; }
    public decimal Open { get; set; }
    public decimal High { get; set; }
    public decimal Low { get; set; }
    public decimal Close { get; set; }
    public decimal Volume { get; set; }
}
public interface IQuote
{
    DateTime Date { get; }
    decimal Open { get; }
    decimal High { get; }
    decimal Low { get; }
    decimal Close { get; }
    decimal Volume { get; }
}

I could just copy data from Aggregate to Quote for all objects but that could be millions of objects, and I have a gut feeling that it is possible to deserialize JSON directly to Quote object in a very concise way, I just could not find how...

CodePudding user response:

you can try this

var json=... your json

List<Quote> quotes = JsonConvert
        .DeserializeObject<Aggregates>(json)
        .Aggregatelist.Select(a => new Quote
        {
            Date = UnixTimeToDateTime(a.StartTime),
            Open = (decimal)a.Open,
            High = (decimal)a.High,
            Low = (decimal)a.Low,
            Close = (decimal)a.Close,
            Volume = (decimal)a.VolumeWeightedw
        }).ToList();

public static DateTime UnixTimeToDateTime(long unixtime)
{
    var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    return dtDateTime.AddMilliseconds(unixtime).ToLocalTime();
}

output

[
  {
    "Date": "2020-01-02T01:30:00-03:30",
    "Open": 74.06,
    "High": 75.15,
    "Low": 73.7975,
    "Close": 75.0875,
    "Volume": 74.6099
  },
  {
    "Date": "2020-01-03T01:30:00-03:30",
    "Open": 74.2875,
    "High": 75.145,
    "Low": 74.125,
    "Close": 74.3575,
    "Volume": 74.7026
  }
]

CodePudding user response:

You should be able to do by extending DefaultContractResolver

class Program
{
    static void Main(string[] args)
    {
        var jsonStr =
            "[{'c': 75.0875,'h': 75.15,'l': 73.7975,'n': 1,'o': 74.06,'t': 1577941200000,'v': 135647456,'vw': 74.6099},"  
            "{'c': 74.3575,'h': 75.145,'l': 74.125,'n': 1,'o': 74.2875,'t': 1578027600000,'v': 146535512,'vw': 74.7026}]";

        var jsonResolver = new PropertyRenameAndIgnoreSerializerContractResolver();
        jsonResolver.RenameProperty(typeof(Quote),  "Close","c");
        jsonResolver.RenameProperty(typeof(Quote), "Volume","vw");
        jsonResolver.RenameProperty(typeof(Quote),  "Open","o");
        jsonResolver.RenameProperty(typeof(Quote),  "Low","l");
        jsonResolver.RenameProperty(typeof(Quote),  "High","h");


        var serializerSettings = new JsonSerializerSettings();
        serializerSettings.ContractResolver = jsonResolver;
        var objList = JsonConvert.DeserializeObject<List<Quote>>(jsonStr, serializerSettings);
    }
}

This is a sample implementation

public class PropertyRenameAndIgnoreSerializerContractResolver : DefaultContractResolver
{
    private readonly Dictionary<Type, HashSet<string>> _ignores;
    private readonly Dictionary<Type, Dictionary<string, string>> _renames;

    public PropertyRenameAndIgnoreSerializerContractResolver()
    {
        _ignores = new Dictionary<Type, HashSet<string>>();
        _renames = new Dictionary<Type, Dictionary<string, string>>();
    }

    public void IgnoreProperty(Type type, params string[] jsonPropertyNames)
    {
        if (!_ignores.ContainsKey(type))
            _ignores[type] = new HashSet<string>();

        foreach (var prop in jsonPropertyNames)
            _ignores[type].Add(prop);
    }

    public void RenameProperty(Type type, string propertyName, string newJsonPropertyName)
    {
        if (!_renames.ContainsKey(type))
            _renames[type] = new Dictionary<string, string>();

        _renames[type][propertyName] = newJsonPropertyName;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        if (IsIgnored(property.DeclaringType, property.PropertyName))
        {
            property.ShouldSerialize = i => false;
            property.Ignored = true;
        }

        if (IsRenamed(property.DeclaringType, property.PropertyName, out var newJsonPropertyName))
            property.PropertyName = newJsonPropertyName;

        return property;
    }

    private bool IsIgnored(Type type, string jsonPropertyName)
    {
        if (!_ignores.ContainsKey(type))
            return false;

        return _ignores[type].Contains(jsonPropertyName);
    }

    private bool IsRenamed(Type type, string jsonPropertyName, out string newJsonPropertyName)
    {
        Dictionary<string, string> renames;

        if (!_renames.TryGetValue(type, out renames) || !renames.TryGetValue(jsonPropertyName, out newJsonPropertyName))
        {
            newJsonPropertyName = null;
            return false;
        }

        return true;
    }
}

and simplified Quote class for this example.

public class Quote 
{
    public decimal Open { get; set; }
    public decimal High { get; set; }
    public decimal Low { get; set; }
    public decimal Close { get; set; }
    public decimal Volume { get; set; }
}

CodePudding user response:

You can use a custom data contractor like so:

    public class QuoteDataContractResolver : DefaultContractResolver
    {
        public static readonly QuoteDataContractResolver Instance = new QuoteDataContractResolver();

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            if (property.DeclaringType == typeof(Quote))
            {
                switch (property.PropertyName)
                {
                    case "Date":
                        property.PropertyName = "d";
                        break;

                    case "Open":
                        property.PropertyName = "o";
                        break;
            
                     //and so on for all the other props
                }

            }
            return property;
        }
    }

And use it like so:

Quote q = new Quote { };
var settings = new JsonSerializerSettings
{
    ContractResolver = QuoteDataContractResolver.Instance
};

//Serialize
var jsonStr = JsonConvert.SerializeObject(q, settings);

//Deserialize
q = JsonConvert.DeserializeObject<Quote>(jsonStr, settings);
  • Related