glideapps / quicktype

Generate types and converters from JSON, Schema, and GraphQL
https://app.quicktype.io
Apache License 2.0
12.41k stars 1.08k forks source link

No map detection when there should be #1639

Open astrohart opened 3 years ago

astrohart commented 3 years ago

Hi,

I have the input JSON

[
    {
        "id": "XTZ",
        "currency": "XTZ",
        "symbol": "XTZ",
        "name": "Tezos",
        "logo_url": "https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/xtz.svg",
        "status": "active",
        "price": "2.97235033",
        "price_date": "2021-01-25T00:00:00Z",
        "price_timestamp": "2021-01-25T20:16:00Z",
        "circulating_supply": "757864529",
        "market_cap": "2252638881",
        "num_exchanges": "82",
        "num_pairs": "205",
        "num_pairs_unmapped": "0",
        "first_candle": "2017-06-23T00:00:00Z",
        "first_trade": "2017-06-23T00:00:00Z",
        "first_order_book": "2018-09-17T00:00:00Z",
        "rank": "22",
        "rank_delta": "0",
        "high": "4.36040127",
        "high_timestamp": "2020-08-12T00:00:00Z",
        "1d": {
            "volume": "372069084.30",
            "price_change": "-0.07157793",
            "price_change_pct": "-0.0235",
            "volume_change": "-140222725.31",
            "volume_change_pct": "-0.2737",
            "market_cap_change": "-53935491.16",
            "market_cap_change_pct": "-0.0234"
        },
        "7d": {
            "volume": "3304750462.89",
            "price_change": "-0.02409163",
            "price_change_pct": "-0.0080",
            "volume_change": "462848715.17",
            "volume_change_pct": "0.1629",
            "market_cap_change": "-16072212.72",
            "market_cap_change_pct": "-0.0071"
        },
        "30d": {
            "volume": "10853300398.22",
            "price_change": "1.00116077",
            "price_change_pct": "0.5079",
            "volume_change": "6277094369.09",
            "volume_change_pct": "1.3717",
            "market_cap_change": "764993226.50",
            "market_cap_change_pct": "0.5142"
        },
        "365d": {
            "volume": "81187651621.54",
            "price_change": "1.41523347",
            "price_change_pct": "0.9089",
            "volume_change": "74747812642.13",
            "volume_change_pct": "11.6071",
            "market_cap_change": "1164985236.66",
            "market_cap_change_pct": "1.0711"
        },
        "ytd": {
            "volume": "10011473978.62",
            "price_change": "0.93886601",
            "price_change_pct": "0.4617",
            "volume_change": "6380987740.25",
            "volume_change_pct": "1.7576",
            "market_cap_change": "716850511.90",
            "market_cap_change_pct": "0.4668"
        }
    }
]

The bottom 5 nested objects are clearly elements in a Dictionary<string, object> of some sort, but QuickType seems to be clueless about it. The code it generates is thus:

// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
//    using Nomics.Api.Models;
//
//    var currenciesTicker = CurrenciesTicker.FromJson(jsonString);

namespace Nomics.Api.Models
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class CurrenciesTicker
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("currency")]
        public string Currency { get; set; }

        [JsonProperty("symbol")]
        public string Symbol { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("logo_url")]
        public Uri LogoUrl { get; set; }

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

        [JsonProperty("price")]
        public string Price { get; set; }

        [JsonProperty("price_date")]
        public DateTimeOffset PriceDate { get; set; }

        [JsonProperty("price_timestamp")]
        public DateTimeOffset PriceTimestamp { get; set; }

        [JsonProperty("circulating_supply")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long CirculatingSupply { get; set; }

        [JsonProperty("market_cap")]
        public string MarketCap { get; set; }

        [JsonProperty("num_exchanges")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long NumExchanges { get; set; }

        [JsonProperty("num_pairs")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long NumPairs { get; set; }

        [JsonProperty("num_pairs_unmapped")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long NumPairsUnmapped { get; set; }

        [JsonProperty("first_candle")]
        public DateTimeOffset FirstCandle { get; set; }

        [JsonProperty("first_trade")]
        public DateTimeOffset FirstTrade { get; set; }

        [JsonProperty("first_order_book")]
        public DateTimeOffset FirstOrderBook { get; set; }

        [JsonProperty("rank")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long Rank { get; set; }

        [JsonProperty("rank_delta")]
        [JsonConverter(typeof(ParseStringConverter))]
        public long RankDelta { get; set; }

        [JsonProperty("high")]
        public string High { get; set; }

        [JsonProperty("high_timestamp")]
        public DateTimeOffset HighTimestamp { get; set; }

        [JsonProperty("1d")]
        public The1_D The1D { get; set; }

        [JsonProperty("7d")]
        public The1_D The7D { get; set; }

        [JsonProperty("30d")]
        public The1_D The30D { get; set; }

        [JsonProperty("365d")]
        public The1_D The365D { get; set; }

        [JsonProperty("ytd")]
        public The1_D Ytd { get; set; }
    }

    public partial class The1_D
    {
        [JsonProperty("volume")]
        public string Volume { get; set; }

        [JsonProperty("price_change")]
        public string PriceChange { get; set; }

        [JsonProperty("price_change_pct")]
        public string PriceChangePct { get; set; }

        [JsonProperty("volume_change")]
        public string VolumeChange { get; set; }

        [JsonProperty("volume_change_pct")]
        public string VolumeChangePct { get; set; }

        [JsonProperty("market_cap_change")]
        public string MarketCapChange { get; set; }

        [JsonProperty("market_cap_change_pct")]
        public string MarketCapChangePct { get; set; }
    }

    public partial class CurrenciesTicker
    {
        public static List<CurrenciesTicker> FromJson(string json) => JsonConvert.DeserializeObject<List<CurrenciesTicker>>(json, Nomics.Api.Models.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this List<CurrenciesTicker> self) => JsonConvert.SerializeObject(self, Nomics.Api.Models.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }

    internal class ParseStringConverter : JsonConverter
    {
        public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);

        public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            var value = serializer.Deserialize<string>(reader);
            long l;
            if (Int64.TryParse(value, out l))
            {
                return l;
            }
            throw new Exception("Cannot unmarshal type long");
        }

        public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
        {
            if (untypedValue == null)
            {
                serializer.Serialize(writer, null);
                return;
            }
            var value = (long)untypedValue;
            serializer.Serialize(writer, value.ToString());
            return;
        }

        public static readonly ParseStringConverter Singleton = new ParseStringConverter();
    }
}

The The1_D class is not what I expect. I expect a class like that, but then a Dictionary<string, The1_D> in the CurrenciesTicker class to appear. I have the Detect maps feature of QuickType toggled on. It does not appear to work.

astrohart commented 3 years ago

So I got away with using an indexer:

namespace Nomics.Api.Models
{
    public class CurrenciesTicker
    {
         /* ... */

        public The1_D this[string index]
        {
           get
           {
              The1_D result;

              switch (index)
              {
                 case "1d" :
                    result = The1D;
                    break;

                 case "7d":
                    result = The7D;
                    break;

                 case "30d":
                    result = The30D;
                    break;

                 case "365d":
                    result = The365D;
                    break;

                 case "ytd":
                    result = Ytd;
                    break;

                 default:
                    throw new ArgumentOutOfRangeException(nameof(index));
              }

              return result;
           }
        }

         /* ... */
    }
}

It depends on knowing the value set in advance, but the input JSON is definitely something I think could benefit from a heuristic that says, if I have 1 or more nested objects named by different properties, it would be cool to have Quicktype automatically generate the indexer as needed.