1

(Skip to bolded part for one-sentence tl;dr:)

I have the JSON object below. Before looking at it, note that:

  • The list of currency pairs like BTC_AMP goes on forever, I cut it off for the sake of example
  • BTC_AMP appears to be a NAMED OBJECT containing some fields.

    {
    "BTC_AMP": {
        "asks": [
            [
                "0.00007400",
                5
            ]
        ],
        "bids": [
            [
                "0.00007359",
                163.59313969
            ]
        ],
        "isFrozen": "0",
        "seq": 38044678
    },
    "BTC_ARDR": {
        "asks": [
            [
                "0.00003933",
                7160.61031389
            ]
        ],
        "bids": [
            [
                "0.00003912",
                1091.21852308
            ]
        ],
        "isFrozen": "0",
        "seq": 16804479
    },
    }
    

I can map the object just fine using Json.NET as described here. My problem is that it seems to me that I need to create an object and predefine property names such as BTC_AMP, BTC_ARDR, and so on for a thousand currency pairs.

You can probably see where I am going with this...how do I map this object without pre-creating every single pair name?

Hoping I am missing something obvious here.

Edit: Code looks like this, what I DON'T want to do:

public class PoloniexPriceVolume
{
    public string Price { get; set; }
    public double Volume { get; set; }
}

public class PoloniexPairInfo
{
    public PoloniexPriceVolume Asks { get; set; }
    public PoloniexPriceVolume Bids { get; set; }
    public bool IsFrozen { get; set; }
    public int Seq { get; set; } 

}

public class PoloniexOrderBook
{
    public PoloniexPairInfo BTC_AMP { get; set; }
    //One thousand and one Arabian currency pairs here
}

Edit 2...can I at least dynamically create an object / an object's properties if I have a list of currency pairs somewhere? Seems less ridiculous than writing it by hand.

3
  • 1
    Just saying: In case the json is well defined, Copy the json to clipboard and in Visual studio open new file then Go to Edit => Paste special => Paste JSON As classes Commented Jul 30, 2017 at 20:07
  • 2
    Use a Dictionary<string, PoloniexPairInfo> as shown in Create a strongly typed c# object from json object with ID as the name. Commented Jul 30, 2017 at 20:14
  • 1
    Doesn't that still create class for each currency object? BTC_AMP, BTC_ARDR and so on? Commented Jul 30, 2017 at 20:15

2 Answers 2

4

You have several issues here:

  • Your root object has a large, variable number of properties whose values correspond to a fixed data type PoloniexPairInfo. Since you don't want to create a root type that hardcodes all these properties, you can deserialize to a Dictionary<string, PoloniexPairInfo> as shown in Create a strongly typed c# object from json object with ID as the name.

  • The Bid and Ask properties are represented in JSON as an array of arrays of values of different types:

    [
      [
        "0.00007359",
        163.59313969
      ]
    ]
    

    You would like to map the inner arrays to a fixed POCO PoloniexPriceVolume by binding values at specific array indices to specific c# properties. You can do this using ObjectToArrayConverter<PoloniexPriceVolume> from C# JSON.NET - Deserialize response that uses an unusual data structure.

  • Finally, the JSON value "isFrozen" has a string value "0" but you would like to map it to a bool value public bool IsFrozen { get; set; }. You can do this by adapting BoolConverter from Convert an int to bool with Json.Net.

Putting all this together, your can deserialize your JSON with the following models and converters:

[JsonConverter(typeof(ObjectToArrayConverter<PoloniexPriceVolume>))]
public class PoloniexPriceVolume
{
    [JsonProperty(Order = 1)]
    public string Price { get; set; }
    [JsonProperty(Order = 2)]
    public double Volume { get; set; }
}

public class PoloniexPairInfo
{
    public List<PoloniexPriceVolume> Asks { get; set; }
    public List<PoloniexPriceVolume> Bids { get; set; }
    [JsonConverter(typeof(BoolConverter))]
    public bool IsFrozen { get; set; }
    public int Seq { get; set; }
}

public class ObjectToArrayConverter<T> : JsonConverter
{
    //https://stackoverflow.com/a/39462464/3744182
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var objectType = value.GetType();
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));
        writer.WriteStartArray();
        foreach (var property in SerializableProperties(contract))
        {
            var propertyValue = property.ValueProvider.GetValue(value);
            if (property.Converter != null && property.Converter.CanWrite)
                property.Converter.WriteJson(writer, propertyValue, serializer);
            else
                serializer.Serialize(writer, propertyValue);
        }
        writer.WriteEndArray();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("invalid type {0}.", objectType.FullName));

        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("token {0} was not JsonToken.StartArray", reader.TokenType));

        // Not implemented: JsonObjectContract.CreatorParameters, serialization callbacks, 
        existingValue = existingValue ?? contract.DefaultCreator();

        using (var enumerator = SerializableProperties(contract).GetEnumerator())
        {
            while (true)
            {
                switch (reader.ReadToContentAndAssert().TokenType)
                {
                    case JsonToken.EndArray:
                        return existingValue;

                    default:
                        if (!enumerator.MoveNext())
                        {
                            reader.Skip();
                            break;
                        }
                        var property = enumerator.Current;
                        object propertyValue;
                        // TODO:
                        // https://www.newtonsoft.com/json/help/html/Properties_T_Newtonsoft_Json_Serialization_JsonProperty.htm
                        // JsonProperty.ItemConverter, ItemIsReference, ItemReferenceLoopHandling, ItemTypeNameHandling, DefaultValue, DefaultValueHandling, ReferenceLoopHandling, Required, TypeNameHandling, ...
                        if (property.Converter != null && property.Converter.CanRead)
                            propertyValue = property.Converter.ReadJson(reader, property.PropertyType, property.ValueProvider.GetValue(existingValue), serializer);
                        else
                            propertyValue = serializer.Deserialize(reader, property.PropertyType);
                        property.ValueProvider.SetValue(existingValue, propertyValue);
                        break;
                }
            }
        }
    }

    static IEnumerable<JsonProperty> SerializableProperties(JsonObjectContract contract)
    {
        return contract.Properties.Where(p => !p.Ignored && p.Readable && p.Writable);
    }
}

public static partial class JsonExtensions
{
    //https://stackoverflow.com/a/39462464/3744182
    public static JsonReader ReadToContentAndAssert(this JsonReader reader)
    {
        return reader.ReadAndAssert().MoveToContentAndAssert();
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

public class BoolConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(((bool)value) ? "1" : "0");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        var token = JToken.Load(reader);

        if (token.Type == JTokenType.Boolean)
            return (bool)token;
        return token.ToString() != "0";
    }

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

And finally, do:

var orderBook = JsonConvert.DeserializeObject<Dictionary<string, PoloniexPairInfo>>(jsonString);

Working .Net fiddle.

Sign up to request clarification or add additional context in comments.

3 Comments

Are you one of those 10x developers I keep hearing about? Thanks a ton. P.S. Dat old-school string interpolation tho.
@VSO - Yeah, unfortunately dotnetfiddle.net doesn't support c# 6.0 syntax yet.
Oh yea, I remember now. It was more interesting to think you are an ancient wizard though. In the best possible sense.
0

You can do something like:

dynamic data = Json.Decode(json);

Then in data you have all your object in runtime.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.