C# Sort JSON string keys
Asked Answered
D

4

9

I'd like to convert the JSON string

"{ \"birthday\": \"1988-03-18\", \"address\": { \"state\": 24, \"city\": 8341, \"country\": 1 } }"

to

"{ \"address\": { \"city\": 8341, \"country\": 1, \"state\": 24 }, \"birthday\": \"1988-03-18\" }"

NOTE: I'm not using the sorted version for communication (because the key order doesn't really matter), I need a sorted version to perform local tests (by comparing JSON strings).


EDIT: I4V pointed a solution that uses Json.Net, I would rather use a solution that doesn't need to include any 3rd party library (actually I'm using the built in System.Json in my application)


I posted a gist with the solution provided by I4V + some testing here. Thank you all.

Duvetyn answered 19/1, 2013 at 18:8 Comment(2)
Hmm... Tempting though it sounds, I'd venture that the better solution would be a slightly deeper inspection of the JSON rather than a string comparision. Given that the enumeration of properties in JS not determined by the spec, the order of object properties should not be relied upon because it's really meaningless to order the properties of a json serialization. ECMA-262, section 12.6.4: The mechanics of enumerating the properties ... is implementation dependent.Social
@Social I agree with you that the JSON key order are meaningless and string comparison shouldn't be used for comparing large/complex JSON objects. But a JSON string sorter may be useful for very specific situations (as mine)Duvetyn
F
20

I will use Json.Net for this

string json = @"{ ""birthday"": ""1988-03-18"", ""address"": { ""state"": 24, ""city"": 8341, ""country"": 1 } }";
var jObj = (JObject)JsonConvert.DeserializeObject(json);
Sort(jObj);
string newJson = jObj.ToString();

void Sort(JObject jObj)
{
    var props = jObj.Properties().ToList();
    foreach (var prop in props)
    {
        prop.Remove();
    }

    foreach (var prop in props.OrderBy(p=>p.Name))
    {
        jObj.Add(prop);
        if(prop.Value is JObject)
            Sort((JObject)prop.Value);
    }
}

EDIT

A try with System.Json but I am not sure about OrderByDescending ( or OrderBy).

var jObj = (System.Json.JsonObject)System.Json.JsonObject.Parse(json);
Sort2(jObj);
var newJson = jObj.ToString();

void Sort2(System.Json.JsonObject jObj)
{
    var props = jObj.ToList();
    foreach (var prop in props)
    {
        jObj.Remove(prop.Key);
    }

    foreach (var prop in props.OrderByDescending(p => p.Key))
    {
        jObj.Add(prop);
        if (prop.Value is System.Json.JsonObject)
            Sort2((System.Json.JsonObject)prop.Value);
    }
}
Fad answered 19/1, 2013 at 18:23 Comment(11)
Interesting. But I'm using the built in System.Json in my app, so I'd rather use a solution that doesn't need a 3rd party lib.Duvetyn
Did you mean to remove the properties from the JObj and not the property list? Looks like you are removing all the items from the property list then trying to sort and enumerate it.Gewirtz
@EduardoCoelho You can still use this solution with System.Json, just instead of deserializing with JSON.NET deserialize with System.Json, check here #9573619Gewirtz
Thanks @I4V, I created a gist with your implementation + basic tests gist.github.com/4575133Duvetyn
Just wanted to say thank you for this. It helped me through a problem.Pouf
It looks like your solution for Json.Net doesn't work for properties of type JArray. Then if(prop.Value is JObject) is not triggering recursive call.Expedite
@OlegDeribas This is a working code. If you think you found a bug, post the json, the code you are using.Fad
@Fad Here is example of JSON with array: { "data": [{ "z": 1, "a": 2 }, { "z": 3, "a": 4 }] }Expedite
@OlegDeribas It is an JObject containing a property data which is an array.Fad
@Fad Yes. Property data is an array of JObjects. And these JObjects' properties don't get sorted because recursive call doesn't go inside JArray.Expedite
Does this approach still work? Stringifying a JObject with ToString just changes the order again it seems, even if the properties are added in the correct order.Brachy
Z
6

I know this may be a little late but, in case of you need to sort the internal arrays of data too (I just needed it):

static void Sort(JObject jObj)
{
    var props = jObj.Properties().ToList();
    foreach (var prop in props)
    {
        prop.Remove();
    }

    foreach (var prop in props.OrderBy(p => p.Name))
    {
        jObj.Add(prop);
        if (prop.Value is JObject)
            Sort((JObject)prop.Value);
        if (prop.Value is JArray)
        {
            Int32 iCount = prop.Value.Count();
            for (Int32 iIterator = 0; iIterator < iCount; iIterator++)
                if (prop.Value[iIterator] is JObject)
                    Sort((JObject)prop.Value[iIterator]);
        }
    }
}

Cheers!

Zondra answered 11/5, 2017 at 3:6 Comment(0)
N
3

By using this approach you can retrieve a dynamic object with your json data

At the DynamicJsonConverter create a SortedDictionary instead

var d = new SortedDictionary<string, object>(dictionary);
// TODO: code to sort inner objects
return new DynamicJsonObject(d);

Then you can use

string jsonStr = "{\"B\":\"2\",\"A\":\"1\"}";
JavaScriptSerializer jss = new JavaScriptSerializer();
jss.RegisterConverters(new JavaScriptConverter[] { new DynamicJsonConverter() });

dynamic json = jss.Deserialize(jsonStr, typeof(object)) as dynamic;

string result = new JavaScriptSerializer().Serialize((json as DynamicJsonObject).Dictionary);

And result will have the expected output.

Nunes answered 19/1, 2013 at 19:15 Comment(1)
Thanks @BrunoLM, however I ended up using the plain System.Json.JsonObject.Duvetyn
T
0

I wanted to sort contents of an array (not the array itself) and missed someone had done it above,

This ones functional, it clones the JSON

static JToken SortFunctional(JToken jt)
{
    if (jt is JObject)
    {
        var jo = (JObject)jt;
        var children =
            from p in jo.Properties()
            orderby p.Name
            select SortFunctional(p);
        return new JObject(children);
    }
    else if (jt is JArray)
    {
        var ja = (JArray)jt;
        var newElements =
            from e in ja
            select SortFunctional(e);
        return new JArray(newElements);
    }
    else if (jt is JProperty)
    {
        var jp = (JProperty)jt;
        return new JProperty(jp.Name,SortFunctional(jp.Value)); 
    }
    else
    {
        // presumably JValue
        return jt.DeepClone();
    }
}

I've used it for about 10 mins, it seems to work, but there maybe some weirdness in there.

Tragus answered 31/5 at 9:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.