How to Deserialize an Array of Values With a Fixed Schema to a Strongly Typed Data Class

How to deserialize an array of values with a fixed schema to a strongly typed data class?

The converter from Deserializing JSON in Visual Basic .NET should do what you need, suitably translated from VB.NET to c#:

public class ObjectToArrayConverter<T> : JsonConverter
{
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
{
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;
}
}

Next, add the converter to your Player class, and indicate the order of each property using JsonPropertyAttribute.Order:

[JsonConverter(typeof(ObjectToArrayConverter<Player>))]
public class Player
{
[JsonProperty(Order = 1)]
public int UniqueID { get; set; }
[JsonProperty(Order = 2)]
public string PlayerDescription { get; set; }
// Other fields as required.
}

Then finally, declare your root object as follows:

public class ScoreboardResults
{
public int timestamp { get; set; }
public int total_players { get; set; }
public int max_score { get; set; }
public Dictionary<string, Player> players { get; set; }
}

Note that I have moved Username out of the Player class and into the dictionary, as a key.

Note that data contract attributes can be used instead of Newtonsoft attributes to specify order:

[JsonConverter(typeof(ObjectToArrayConverter<Player>))]
[DataContract]
public class Player
{
[DataMember(Order = 1)]
public int UniqueID { get; set; }
[DataMember(Order = 2)]
public string PlayerDescription { get; set; }
// Other fields as required.
}

Demo fiddles here, here and here.

c# Deserialize unlabelled JSON array

You can use the custom JsonConverter ObjectToArrayConverter<Full_Result> from this answer to C# JSON.NET - Deserialize response that uses an unusual data structure to deserialize your JSON into your existing typed data model. Modify Full_Result as follows:

[JsonConverter(typeof(ObjectToArrayConverter<Full_Result>))]
public class Full_Result
{
[JsonProperty(Order = 1)]
public IList<string> values { get; set; }
[JsonProperty(Order = 2)]
public float score { get; set; }
}

And you will now be able to deserialize as follows:

Parsed_JSON result = JsonConvert.DeserializeObject<Parsed_JSON>(JSON);

Notes:

  • ObjectToArrayConverter<T> works by mapping the serializable members of T to an array, where the array sequence is defined by the value of the JsonPropertyAttribute.Order attribute applied to each member. Data contract attributes with DataMemberAttribute.Order set could be used instead, if you prefer.

  • In your JSON the "score" values are not actually numbers:

    score_1
    score_2

    I am assuming that this is a typo in the question and that these values are in fact well-formed numbers as defined by the JSON standard.

Sample fiddle here.

How to deserialize a JSON array into an object using Json.Net?

Json.Net does not have a facility to automatically map an array into a class. To do so you need a custom JsonConverter. Here is a generic converter that should work for you. It uses a custom [JsonArrayIndex] attribute to identify which properties in the class correspond to which indexes in the array. This will allow you to easily update your model if the JSON changes. Also, you can safely omit properties from your class that you don't need, such as Filler.

Here is the code:

public class JsonArrayIndexAttribute : Attribute
{
public int Index { get; private set; }
public JsonArrayIndexAttribute(int index)
{
Index = index;
}
}

public class ArrayToObjectConverter<T> : JsonConverter where T : class, new()
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);

var propsByIndex = typeof(T).GetProperties()
.Where(p => p.CanRead && p.CanWrite && p.GetCustomAttribute<JsonArrayIndexAttribute>() != null)
.ToDictionary(p => p.GetCustomAttribute<JsonArrayIndexAttribute>().Index);

JObject obj = new JObject(array
.Select((jt, i) =>
{
PropertyInfo prop;
return propsByIndex.TryGetValue(i, out prop) ? new JProperty(prop.Name, jt) : null;
})
.Where(jp => jp != null)
);

T target = new T();
serializer.Populate(obj.CreateReader(), target);

return target;
}

public override bool CanWrite
{
get { return false; }
}

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

To use the converter, you need to mark up your ChildModel class as shown below:

[JsonConverter(typeof(ArrayToObjectConverter<ChildModel>))]
class ChildModel
{
[JsonArrayIndex(0)]
public int ID { get; set; }
[JsonArrayIndex(1)]
public string StatusId { get; set; }
[JsonArrayIndex(2)]
public DateTime ContactDate { get; set; }
[JsonArrayIndex(3)]
public string State { get; set; }
[JsonArrayIndex(4)]
public string Status { get; set; }
[JsonArrayIndex(5)]
public string CustomerName { get; set; }
[JsonArrayIndex(6)]
public DateTime WorkStartDate { get; set; }
[JsonArrayIndex(7)]
public DateTime WorkEndDate { get; set; }
[JsonArrayIndex(8)]
public string Territory { get; set; }
[JsonArrayIndex(9)]
public string CustType { get; set; }
[JsonArrayIndex(10)]
public int JobOrder { get; set; }
[JsonArrayIndex(12)]
public string Link { get; set; }
}

Then just deserialize as usual and it should work as you wanted. Here is a demo: https://dotnetfiddle.net/n3oE3L

Note: I did not implement WriteJson, so if you serialize your model back to JSON, it will not serialize back to the array format; instead it will use the default object serialization.

serialzie c# object to json array with different types

I'm not sure that there is a build in way, but you can write your own JsonConverter using some reflection magic:

public class ChartValueToArrayJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ChartValue);
}

public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
if (value == null) return;

writer.WriteStartArray();

var properties = value.GetType().GetProperties();
foreach (var property in properties)
writer.WriteValue(value.GetType().GetProperty(property.Name).GetValue(value));

writer.WriteEndArray();
}
}

Usage:

JsonConvert.SerializeObject(new ChartValue(), new ChartValueToArrayJsonConverter()) // results in ["0001-01-01T00:00:00",0.0]

or mark your ChartValue class with [JsonConverterAttribute(typeof(ChartValueToArrayJsonConverter))] attribute if you want this behavior globally.

Unable to de-serialize json string

Your model should be:

public class Data
{
public List<List<object>> MyValues { get; set; }
}

public class MyData
{
public string status { get; set; }
public Data data { get; set; }
}

And then serialize and access to the datas:

using (StreamReader file = System.IO.File.OpenText(@"jd.txt"))
{
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
MyData MyData = (MyData)serializer.Deserialize(file, typeof(MyData));
return MyData.data.MyValues[0][0].ToString();
}

How to deserialize array into wrapper object?

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:

  • Define an explicit data model for your data.
  • Label each property with [JsonProperty(Order = N)] to indicate relative array positions. ([DataContract] and [DataMember(Order = N)] could be used instead.)
  • Use the converter ObjectToArrayConverter<ArrayWrapper>() from this answer to C# JSON.NET - Deserialize response that uses an unusual data structure.

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.



Related Topics



Leave a reply



Submit