Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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());
        }
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
139 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share

548k questions

547k answers

4 comments

86.3k users

...