-2

I have a json data which was not serialized properly. I have attached the expected and bad one.

I need to handle the bad json, get in a right format

Expected

"channels": {
    "heart-rate": {
        "events": {
            "type": "Project.Model.ChannelEvents.HeartRateChannelEvent, Project, Version=1.2.7.0, Culture=neutral, PublicKeyToken=null",
            "structure": [
                "beatsPerMinute",
                "offset"
            ],
            "list": [
                [
                    40,
                    0
                ]
            ]
        }
    },
    "location": {
        "events": {
            "type": "Project.Model.ChannelEvents.LocationChannelEvent, Project, Version=1.2.7.0, Culture=neutral, PublicKeyToken=null",
            "structure": [
                "latitude",
                "longitude",
                "offset"
            ],
            "list": [
                [
                    0.0,
                    0.0,
                    0
                ]
            ]
        }

    }
}

badjson This is the bad json data which needs to be formatted, in a console application

"channels": {
    "heart-rate": {
        "events": {
            "$type": "System.Collections.Generic.List`1[[Project.Model.Activity+Channel+Event, Project]], mscorlib",
            "$values": [{
                    "$type": "Project.Model.ChannelEvents.HeartRateChannelEvent, Project",
                    "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
                }
            ]
        }
    }
}
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
kishore
  • 35
  • 1
  • 1
  • 6
  • 1
    Kindly check the first posted JSON data,it contains errors. – Hameed Syed Mar 31 '17 at 16:36
  • what is bad about your bad json? – victor Mar 31 '17 at 16:39
  • @victor- if you look into the first, where i have structure and list- i see those missing in bad data – kishore Mar 31 '17 at 16:41
  • Do you have the code you serialized it with? – ProgrammingLlama Mar 31 '17 at 16:46
  • It looks like the two JSON strings were serialized using different serializers; the easiest thing would be to find out what the right serializer to use is, and then to deserialize the 'bad' JSON using the serializer that understands it, and re-serialize it using the 'good' serializer. The first JSON looks pretty non-standard so if it was custom code, than you'll need to replicate or reuse that code. – Alex Paven Mar 31 '17 at 16:57
  • public string Type { get; set; } public IList Events { get; set; } public abstract class Event { public uint Offset { get; set; } } } – kishore Mar 31 '17 at 16:57
  • Both your good and bad JSON are syntactically invalid: they are missing outer braces `{` and `}`. Upload to http://jsonlint.com/ and you will see an error reported. Is your JSON actually wrong in this manner, or is this a typo in your question? – dbc Mar 31 '17 at 19:45

2 Answers2

1

Firstly, both your input and output JSON are syntactically invalid: they are missing outer braces { and }. For the remainder of this answer, I'm going to assume this is a typo in the question.

Assuming you have not done so already, you could install as shown here and then use LINQ to JSON to load and modify your JSON. Using this approach avoids the need to define c# types that perfectly match your JSON.

Your input JSON has two problems:

  • The tokens "channels.heart-rate.events" and "channels.location.events" are arrays of objects for which Json.NET type information has been included. (It is clear from the presence of the "$type" property that the JSON was originally generated using Json.NET)

    What you want instead is for these arrays to be reformatted into a single object that contains the item type, an array of item property names, and an array of array of item property values.

  • The type information has been formatted using TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple. You want to add assembly information, converting to FormatterAssemblyStyle.Full format.

Both of these can be corrected using LINQ-to-JSON. Say you have two streams, Stream inputStream and Stream outputStream corresponding to a stream with the JSON to be fixed and a stream in which to store the fixed JSON. Then, introduce the following utility methods:

public static class JsonExtensions
{
    const string JsonTypeName = @"$type";
    const string JsonValuesName = @"$values";

    public static void ReformatCollections(Stream inputStream, Stream outputStream, IEnumerable<string> paths, Func<string, string> typeNameMapper, Formatting formatting)
    {
        var root = JToken.Load(new JsonTextReader(new StreamReader(inputStream)) { DateParseHandling = DateParseHandling.None });
        root = ReformatCollections(root, paths, typeNameMapper);

        var writer = new StreamWriter(outputStream);
        var jsonWriter = new JsonTextWriter(writer) { Formatting = formatting };

        root.WriteTo(jsonWriter);
        jsonWriter.Flush();
        writer.Flush();
    }

    public static JToken ReformatCollections(JToken root, IEnumerable<string> paths, Func<string, string> typeNameMapper)
    {
        foreach (var path in paths)
        {
            var token = root.SelectToken(path);
            var newToken = token.ReformatCollection(typeNameMapper);
            if (root == token)
                root = newToken;
        }

        return root;
    }

    public static JToken ReformatCollection(this JToken value, Func<string, string> typeNameMapper)
    {
        if (value == null || value.Type == JTokenType.Null)
            return value;

        var array = value as JArray;
        if (array == null)
            array = value[JsonValuesName] as JArray;
        if (array == null)
            return value;

        // Extract the item $type and ordered set of properties.
        string type = null;
        var properties = new Dictionary<string, int>();
        foreach (var item in array)
        {
            if (item.Type == JTokenType.Null)
                continue;
            var obj = item as JObject;
            if (obj == null)
                throw new JsonSerializationException(string.Format("Item \"{0}\" was not a JObject", obj.ToString(Formatting.None)));
            var objType = (string)obj[JsonTypeName];
            if (objType != null && type == null)
                type = objType;
            else if (objType != null && type != null)
            {
                if (type != objType)
                    throw new JsonSerializationException("Too many item types.");
            }
            foreach (var property in obj.Properties().Where(p => p.Name != JsonTypeName))
            {
                if (!properties.ContainsKey(property.Name))
                    properties.Add(property.Name, properties.Count);
            }
        }
        var propertyList = properties.OrderBy(p => p.Value).Select(p => p.Key).ToArray();
        var newValue = new JObject();
        if (type != null)
            newValue["type"] = JToken.FromObject(typeNameMapper(type));
        newValue["structure"] = JToken.FromObject(propertyList);
        newValue["list"] = JToken.FromObject(array
            .Select(o => (o.Type == JTokenType.Null ? o : propertyList.Where(p => o[p] != null).Select(p => o[p]))));
        if (value.Parent != null)
            value.Replace(newValue);
        return newValue;
    }
}

Then, at the top level of your console method, you can fix your JSON as follows:

    Func<string, string> typeNameMapper = (t) =>
        {
            if (!t.EndsWith(", Version=1.2.7.0, Culture=neutral, PublicKeyToken=null"))
                t = t + ", Version=1.2.7.0, Culture=neutral, PublicKeyToken=null";
            return t;
        };
    var paths = new[]
        {
            "channels.heart-rate.events",
            "channels.location.events"
        };
    JsonExtensions.ReformatCollections(inputStream, outputStream, paths, typeNameMapper, Formatting.Indented);

Sample fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    Let me try this. This looks helpful – kishore Apr 01 '17 at 02:24
  • +1 The values are converting but i am not able to deserialize the output json, when i change the latitude nad longitude values @dbc – kishore Apr 02 '17 at 17:58
  • @kishore - then you might want to ask a second question, or otherwise provide a [mcve] that demonstrates your new problem. As it is your question is about transforming one JSON format to another, and doesn't really involve deserialization at all. – dbc Apr 02 '17 at 18:02
  • link to my new question @dbc http://stackoverflow.com/questions/43171814/unable-to-deserialize-my-json – kishore Apr 02 '17 at 18:14
  • I just have another one, Can i take our type,structure,list for one path and leave it for other path byt $type ,$values should be transformed anyway – kishore Apr 03 '17 at 16:18
0

Use Json.NET to Serialize and Deserialize your JSON

McKabue
  • 2,076
  • 1
  • 19
  • 34