1

I need to remove the outer node of a JSON. So an example would be :

{
    app: {
       ...
    }
}

Any ideas on how to remove the outer node, so we get only

{
   ...
}

WITHOUT using JSON.NET, only tools in the .NET Framework (C#).

In Json.NET I used:

JObject.Parse(json).SelectToken("app").ToString();

Alternatively, any configuration of the DataContractJsonSerializer, so that it ignores the root when deserializing, would also work. The way I do the desrialization now is:

protected T DeserializeJsonString<T>(string jsonString)
        {
            T tempObject = default(T);

            using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
            {
                var serializer = new DataContractJsonSerializer(typeof(T));
                tempObject = (T)serializer.ReadObject(memoryStream);
            }

            return tempObject;
        }

Note that the root object's property name can differ from case to case. For example it can be "transaction".

Thanks for any suggestion.

dbc
  • 104,963
  • 20
  • 228
  • 340
Vlad
  • 536
  • 1
  • 5
  • 18
  • Does your root object always has `app` as a property? Or do you have to support other properties as well? – rene Apr 10 '16 at 11:18
  • can be different, for example it can be "transaction" – Vlad Apr 10 '16 at 11:33
  • why you can't use JSON.NET? It became the standard JSON serialization library since many years, ASP.NET WebAPI uses it internally. Any built-in framework JSON serializer is outdated and it can be slower and with less features than JSON.NET – Matías Fidemraizer Apr 10 '16 at 11:38

1 Answers1

3

There is no equivalent to SelectToken built into .Net. But if you simply want to unwrap an outer root node and do not know the node name in advance, you have the following options.

  1. If you are using .Net 4.5 or later, you can deserialize to a Dictionary<string, T> with DataContractJsonSerializer.UseSimpleDictionaryFormat = true:

    protected T DeserializeNestedJsonString<T>(string jsonString)
    {
        using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonString)))
        {
            var serializer = new DataContractJsonSerializer(typeof(Dictionary<string, T>));
            serializer.UseSimpleDictionaryFormat = true;
            var dictionary = (Dictionary<string, T>)serializer.ReadObject(memoryStream);
            if (dictionary == null || dictionary.Count == 0)
                return default(T);
            else if (dictionary.Count == 1)
                return dictionary.Values.Single();
            else
            {
                throw new InvalidOperationException("Root object has too many properties");
            }
        }
    }
    

    Note that if your root object contains more than one property, you cannot deserialize to a Dictionary<TKey, TValue> to get the first property since the order of the items in this class is undefined.

  2. On any version of .Net that supports the data contract serializers, you can take advantage of the fact that DataContractJsonSerializer inherits from XmlObjectSerializer to call JsonReaderWriterFactory.CreateJsonReader() to create an XmlReader that actually reads JSON, then skip forward to the first nested "element":

    protected T DeserializeNestedJsonStringWithReader<T>(string jsonString)
    {
        var reader = JsonReaderWriterFactory.CreateJsonReader(Encoding.Unicode.GetBytes(jsonString), System.Xml.XmlDictionaryReaderQuotas.Max);
        int elementCount = 0;
    
        while (reader.Read())
        {
            if (reader.NodeType == System.Xml.XmlNodeType.Element)
                elementCount++;
            if (elementCount == 2) // At elementCount == 1 there is a synthetic "root" element
            {
                var serializer = new DataContractJsonSerializer(typeof(T));
                return (T)serializer.ReadObject(reader, false);
            }
        }
        return default(T);
    }
    

    This technique looks odd (parsing JSON with an XmlReader?), but with some extra work it should be possible to extend this idea to create SAX-like parsing functionality for JSON that is similar to SelectToken(), skipping forward in the JSON until a desired property is found, then deserializing its value.

    For instance, to select and deserialize specific named properties, rather than just the first root property, the following can be used:

    public static class DataContractJsonSerializerExtensions
    {
        public static T DeserializeNestedJsonProperty<T>(string jsonString, string rootPropertyName)
        {
            // Check for count == 2 because there is a synthetic <root> element at the top.
            Predicate<Stack<string>> match = s => s.Count == 2 && s.Peek() == rootPropertyName;
            return DeserializeNestedJsonProperties<T>(jsonString, match).FirstOrDefault();
        }
    
        public static IEnumerable<T> DeserializeNestedJsonProperties<T>(string jsonString, Predicate<Stack<string>> match)
        {
            DataContractJsonSerializer serializer = null;
            using (var reader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(jsonString), XmlDictionaryReaderQuotas.Max))
            {
                var stack = new Stack<string>();
                while (reader.Read())
                {
                    if (reader.NodeType == System.Xml.XmlNodeType.Element)
                    {
                        stack.Push(reader.Name);
                        if (match(stack))
                        {
                            serializer = serializer ?? new DataContractJsonSerializer(typeof(T));
                            yield return (T)serializer.ReadObject(reader, false);
                        }
                        if (reader.IsEmptyElement)
                            stack.Pop();
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement)
                    {
                        stack.Pop();
                    }
                }
            }
        }
    }
    

    See Mapping Between JSON and XML for details on how JsonReaderWriterFactory maps JSON to XML.

dbc
  • 104,963
  • 20
  • 228
  • 340