1

I want Json.NET deserializer to be able to directly use: ArrayWrapper[] Array { get; set; } property.

Should I write custom JsonConverter or there is easier way?

public class Obj {

    //public ArrayWrapper[] Array { get; set; } // I want it to work!!!

    [JsonProperty( "array" )]
    public int[][] Array_ { get; set; }

    [JsonIgnore]
    public ArrayWrapper[] Array => Array_.Select( i => new ArrayWrapper( i ) ).ToArray();

}

public struct ArrayWrapper {

    private readonly int[] array;

    public int Item0 => array[ 0 ];
    public int Item1 => array[ 1 ];

    public ArrayWrapper(int[] array) {
        this.array = array;
    }

    public static implicit operator ArrayWrapper(int[] array) {
        return new ArrayWrapper( array );
    }
}

Note: the array of arrays is returned by this API: https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data. I want to convert inner array into object.

dbc
  • 104,963
  • 20
  • 228
  • 340
Denis535
  • 3,407
  • 4
  • 25
  • 36
  • Or for example I want to wrap root array into object with array property. – Denis535 Jun 08 '19 at 18:43
  • 1
    1) What JSON are you trying to serialize or deserialize? Can you please [edit] your question to show your desired JSON? 2) `ArrayWrapper` has no public getter for `array`, only for the first two entries (which are assumed to exist). What should happen when the array is the wrong length? – dbc Jun 08 '19 at 19:03
  • 1
    OK, still not sure I understand your question, but if I do the easiest way to do this is 1) make `ArrayWrapper` implement `IEnumerable`, and 2) add a `public ArrayWrapper(IEnumerable enumerable)` constructor. Once done, Json.NET will treat `ArrayWrapper` as a read-only collection and (de) serialize it as such. Demo here: https://dotnetfiddle.net/jHHx86. Is that what you want? – dbc Jun 08 '19 at 19:15
  • This API returns array of arrays: https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#klinecandlestick-data I want to convert inner array into object. Thank you for your answer. I guess I'll have to use it. But this is not the perfect solution (: – Denis535 Jun 08 '19 at 19:52
  • *But this is not the perfect solution (:* - then should I add an answer? Or can you [edit] your question to explain your requirements more clearly? There are already answers about deserializing Binance Kline/Candlestick data, see [C#: Parsing a non-JSON array-only api response to a class object with x properties](https://stackoverflow.com/a/48431099), – dbc Jun 08 '19 at 20:34
  • Yes, add your answer. – Denis535 Jun 08 '19 at 20:43

2 Answers2

1

If you simply want to capture a collection inside a surrogate wrapper object, the easiest way to do so is to make the wrapper appear to be a read-only collection to Json.NET. To do that, you must:

  • Implement IEnumerable<T> for some T (here int).
  • Add a constructor that takes an IEnumerable<T> for the same T. (From experimentation, a constructor that takes T [] is not sufficient.)

Thus if you define your ArrayWrapper as follows:

public struct ArrayWrapper : IEnumerable<int>
{
    private readonly int[] array;

    public int Item0 { get { return array[ 0 ]; } }
    public int Item1 { get { return array[ 1 ]; } }

    public ArrayWrapper(int[] array) {
        this.array = array;
    }

    public ArrayWrapper(IEnumerable<int> enumerable) 
    {
        this.array = enumerable.ToArray();
    }

    public static implicit operator ArrayWrapper(int[] array) {
        return new ArrayWrapper( array );
    }

    public IEnumerator<int> GetEnumerator()
    {
        return (array ?? Enumerable.Empty<int>()).GetEnumerator();
    }

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

You will be able to serialize and deserialize Obj into the following JSON:

{"Array":[[1,101]]}

Demo fiddle #1 here.

However, in comments you mention your array actually has a fixed schema as documented in Public Rest API for Binance: Kline/Candlestick data. If so, you could adopt the approach from this answer to C#: Parsing a non-JSON array-only api response to a class object with x properties which specifically addresses Binance Kline/Candlestick data:

I.e. for the specific model shown in your question, modify its definition as follows:

[JsonConverter(typeof(ObjectToArrayConverter<ArrayWrapper>))]
public struct ArrayWrapper
{
    [JsonProperty(Order = 1)]
    public int Item0 { get; set; }
    [JsonProperty(Order = 2)]
    public int Item1 { get; set; }
}

And you will be able to (de)serialize the same JSON. Note that the converter is entirely generic and can be reused any time the pattern of (de)serializing an array with a fixed schema into an object arises.

(You might also want to change the struct to a class since mutable structs are discouraged.)

Demo fiddles #2 here and #3 here showing the use of a JsonConverter attribute applied to one of the serializable properties.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • I chose the second option and got the problem. My property has `JsonConverter` but `ArrayToObjectConverter` doesn't use it and throw exception. The problem in line `jToken.ToObject( prop.PropertyType, serializer )`. jToken is long but PropertyType is DateTime. – Denis535 Jun 09 '19 at 11:20
  • I guess I should convert JArray into JObject. – Denis535 Jun 09 '19 at 20:01
  • @wishmaster35 - answer updated, however in the future you really should ask another question **with a new [mcve] that demonstrates your problem** since the initial answer met the needs of your original question. – dbc Jun 10 '19 at 22:18
0

I made my version of ArrayToObjectConverter.

My ArrayToObjectConverter just convert JArray into JObject with properties: Item1, Item2, Item3... and deserialize this object.

public class ArrayToObjectConverter : JsonConverter {

    public override bool CanRead => true;
    public override bool CanWrite => true;


    public override bool CanConvert(Type type) {
        return false;
    }

    public override object ReadJson(JsonReader reader, Type type, object existingInstance, JsonSerializer serializer) {
        var contract = (JsonObjectContract) serializer.ContractResolver.ResolveContract( type );
        var instance = existingInstance ?? contract.DefaultCreator();

        var array = JArray.Load( reader );
        var obj = Convert( array );
        serializer.Populate( obj.CreateReader(), instance );
        return instance;
    }

    public override void WriteJson(JsonWriter writer, object instance, JsonSerializer serializer) {
        var contract = (JsonObjectContract) serializer.ContractResolver.ResolveContract( instance.GetType() );
        var properties = contract.Properties.Where( IsSerializable );

        var values = properties.Select( i => i.ValueProvider.GetValue( instance ) );
        serializer.Serialize( writer, values );
    }


    // Helpers
    private static JObject Convert(JArray array) {
        var obj = new JObject();
        for (var i = 0; i < array.Count; i++) {
            obj.Add( "Item" + i, array[ i ] );
        }
        return obj;
    }

    private static bool IsSerializable(JsonProperty property) {
        return !property.Ignored && property.Readable && property.Writable;
    }


}

You should use ArrayToObjectConverter with objects like this:

[JsonConverter( typeof( ArrayToObjectConverter ) )]
public class Obj {

    [JsonProperty]
    internal string Item0 { get; set; }
    [JsonProperty]
    internal decimal Item1 { get; set; }

    [JsonIgnore]
    public string Text { get => Item0; set => Item0 = value; }
    [JsonIgnore]
    public decimal Number { get => Item1; set => Item1 = value; }

}

Only I'm not sure if JsonObjectContract.Properties will always have right order or not.

Denis535
  • 3,407
  • 4
  • 25
  • 36