-1

This is my class

Initial one which actually needs to be transformed to good json data. This is the initial bad one

{
    "channels": {
        "heart-rate": {
            "events": {
                "$type": "System.Collections.Generic.List`1[[Project.Model.Activity+Channel+Event, Project]], mscorlib",
                "$values": [{
                        "$type": "Project.Model.ChannelEvents.HeartRateChannelEvent, LTF.MyPlan.ActivityUtil",
                        "beatsPerMinute": 40,
                        "offset": 0
                    }
                ]
            }
        },
        "location": {
            "events": {
                "$type": "System.Collections.Generic.List`1[[Project.Model.Activity+Channel+Event, Project]], mscorlib",
                "$values": [{
                        "$type": "Project.Model.ChannelEvents.LocationChannelEvent, Project",
                        "latitude": 0.0,
                        "longitude": 0.0,
                        "offset": 0
                    }
                ]
            }
        }
    }
}

public class LocationChannelEvent : Activity.Channel.Event    
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public float? Distance { get; set; }
    public float? Altitude { get; set; }

    /// <summary>
    /// Speed in m/s
    /// </summary>
    public float? Speed { get; set; }

This is my json data, which i am unable to deserialize. I keep receiving the default values, even when i change

{


"location": {
            "events": {
                "$type": "System.Collections.Generic.List`1[[Project.Model.Activity+Channel+Event, Project]], mscorlib",
                "$values": [{
                        "$type": "Project.Model.ChannelEvents.LocationChannelEvent, Project",
                        "latitude": 0.0,
                        "longitude": 0.0,
                        "offset": 0
            ]
        }
    }
}

My Custom Coverter

public class CompactListConverter : JsonConverter
    {
        public const string TypeKey = "type";
        public const string StructureKey = "structure";
        public const string ListKey = "list";

    /// <summary>
    /// Only convert lists of non-enumerable class types.
    /// </summary>
    /// <param name="objectType"></param>
    /// <returns></returns>
    public override bool CanConvert(Type objectType)
    {
        var objectTypeInfo = objectType.GetTypeInfo();

        if (objectTypeInfo.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>))
        {
            var itemTypeInfo = objectTypeInfo.GenericTypeArguments.Single().GetTypeInfo();
            if (itemTypeInfo.IsClass && !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(itemTypeInfo))
            {
                return true;
            }
        }
        return false;
    }

    /// <summary>
    ///  Generates a wrapper object containing type, structure, and the condensed list.
    /// </summary>
    /// <param name="writer"></param>
    /// <param name="value"></param>
    /// <param name="serializer"></param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var list = (IList)value;
        if (list.Count > 0)
        {
            var array = new JArray();
            var wrapper = GetWrapper(list, serializer);

            foreach (var item in list)
            {
                var obj = JObject.FromObject(item, serializer);
                var itemValues = new JArray();
                foreach (var prop in obj.Properties())
                {
                    itemValues.Add(prop.Value);
                }
                array.Add(itemValues);
            }

            wrapper.Add(ListKey, array);
            wrapper.WriteTo(writer);
        }
        else
        {
            new JObject().WriteTo(writer);
        }
    }

    private JObject GetWrapper(IList list, JsonSerializer serializer)
    {
        var wrapper = new JObject {{TypeKey, list[0].GetType().AssemblyQualifiedName}};

        var keys = new JArray();

        var first = JObject.FromObject(list[0], serializer);
        foreach (var prop in first.Properties())
        {
            keys.Add(new JValue(prop.Name));
        }

        wrapper.Add(StructureKey, keys);

        return wrapper;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var wrapper = JObject.Load(reader);
        var itemType = Type.GetType(wrapper.GetValue(TypeKey).ToObject<string>());
        var array = wrapper.GetValue(ListKey) as JArray;

        var list = existingValue as IList ?? (IList) Activator.CreateInstance(typeof (List<>).MakeGenericType(new[] {itemType}));

        if (array != null && array.Count > 0)
        {
            var keys = wrapper.GetValue(StructureKey) as JArray ?? new JArray();
            foreach (var itemValues in array.Children<JArray>())
            {
                var item = new JObject();
                for (var i = 0; i < keys.Count; i++)
                {
                    item.Add(new JProperty(keys[i].ToString(), itemValues[i]));
                }

                list.Add(item.ToObject(itemType, serializer));
            }
        }
        return list;
    }
}


public class ChannelCompactingConverter : CompactListConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return base.CanConvert(objectType) 
                && typeof(IList<Activity.Channel.Event>).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
        }
    }
kishore
  • 35
  • 1
  • 1
  • 6
  • There's no reason why it would work. Using e.g. [tag:json.net], if you look at the [docs](http://www.newtonsoft.com/json/help/html/SerializationGuide.htm) you will see that your JSON should look something like the **badjson** from your [original question](https://stackoverflow.com/q/43145603/3744182), if you enable [`TypeNameHandling`](http://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm). Why not just deserialize the **badjson** directly without transforming it? – dbc Apr 02 '17 at 19:43
  • Where exactly do i use this this. typenamehandling-should i create a new deserialization class – kishore Apr 02 '17 at 19:56
  • Are you asking how to directly deserialize your **badjson** from your previous question, without transforming it? – dbc Apr 02 '17 at 19:59
  • I wanted to transform it too good json and deserialize back- as i need to save it back to db – kishore Apr 02 '17 at 20:01
  • Are you sure you are not trying to solve an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) here? Your original so-called **badjson** can be easily deserialized, why transform it first? – dbc Apr 02 '17 at 20:08
  • Okay, i was not sure of that, now how could i deserialize first and remove the $type and $values and get type,structure,list as previous question – kishore Apr 02 '17 at 20:13

2 Answers2

1

You can use to deserialize and re-serialize a List<LocationChannelEvent> in the format shown as long as you use a custom JsonConverer. This is required because, by default, a collection of objects is serialized from and to a JSON array, but in your JSON, a collection of objects is being serialized in a slightly more compact form of a single object where the object property names are serialized only once in an array of strings called "structure", and the objects themselves are represented as an array of array of values, the inner arrays being in 1-1 correspondence to the structure array.

Thus, if you create the following converter:

public class StructuredListConverter<T> : JsonConverter
{
    const string typeName = "type";
    const string structureName = "structure";
    const string listName = "list";

    public override bool CanConvert(Type objectType)
    {
        if (!typeof(ICollection<T>).IsAssignableFrom(objectType))
            return false;
        // This converter is only implemented for read/write collections.  So no arrays.
        if (objectType.IsArray)
            return false; 
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var collection = existingValue as ICollection<T> ?? (ICollection<T>) serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        var root = JObject.Load(reader);
        var structure = root[structureName] == null ? null : root[structureName].ToObject<string []>();
        if (structure == null)
            throw new JsonSerializationException("structure not found.");
        var listToken = root[listName];
        if (listToken == null || listToken.Type == JTokenType.Null)
            return collection;
        var list = listToken as JArray;
        if (list == null)
            throw new JsonSerializationException("list was not an array.");
        if (list == null || list.Count == 0)
            return collection;
        foreach (var item in list)
        {
            if (item == null || item.Type == JTokenType.Null)
                collection.Add(default(T));
            else if (item.Type != JTokenType.Array)
                throw new JsonSerializationException(string.Format("Item was not an array: {0}", item));
            else
                collection.Add(new JObject(item.Zip(structure, (i, n) => new JProperty(n, i))).ToObject<T>());
        }
        return collection;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(typeof(T)) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("Type {0} is not mapped to a JSON object.", typeof(T)));

        var collection = (ICollection<T>)value;
        writer.WriteStartObject();

        // Write item type
        writer.WritePropertyName(typeName);
        serializer.Serialize(writer, typeof(T));

        // Write structure (property names)
        var structure = contract.Properties.Where(p => p.Readable && !p.Ignored).Select(p => p.PropertyName).ToList();
        writer.WritePropertyName(structureName);
        serializer.Serialize(writer, structure);

        // Write array of array of values
        var query = collection
            .Select(i => i == null ? null : contract.Properties.Where(p => p.Readable && !p.Ignored).Select(p => p.ValueProvider.GetValue(i)));
        writer.WritePropertyName(listName);
        serializer.Serialize(writer, query);

        writer.WriteEndObject();
    }
}

And define your data model as follows:

public class LocationChannelEvent : Activity.Channel.Event
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
    public float? Distance { get; set; }
    public float? Altitude { get; set; }

    /// <summary>
    /// Speed in m/s
    /// </summary>
    public float? Speed { get; set; }
}

public class Location
{
    [JsonConverter(typeof(StructuredListConverter<LocationChannelEvent>))]
    public List<LocationChannelEvent> events { get; set; }
}

public class RootObject
{
    public Location location { get; set; }
}

You will be able to deserialize and re-serialize the JSON shown.

Prototype fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • We have a custom converter already built in Utility.Let me add it to question – kishore Apr 02 '17 at 21:05
  • @kishore - this is now an entirely different question, all my work above would seem to have been a waste of time. You will need to debug your converter to determine the problem is. If you cannot determine the problem, then ask. See https://stackoverflow.com/help/how-to-ask – dbc Apr 02 '17 at 21:21
  • sorry for the trouble – kishore Apr 02 '17 at 22:22
  • my converter is working fine for everything, but some data is corrupted like the one which i displayed as bad json data. i somehow need to set it back to good json – kishore Apr 02 '17 at 22:23
0

Well as far as I'm familiar with json these two things the class and the json are two complete different things! So it's clear that it can't be serialize as that.

What I do in such cases is that I deserialize the json as a dynamic object and then see the object that C# creates and compare it with my class. You can do the same thing like this:

dynamic res = JsonConvert.Deserialize(json);

You can then place a breakpoint here and see what deserializer gives you.

Emad
  • 3,809
  • 3
  • 32
  • 44