2
\$\begingroup\$

I am receiving the JSON from WebService and populating ExpandoObject from this one:

 var converter = new ExpandoObjectConverter();
 var jsonObject = JsonConvert.DeserializeObject<ExpandoObject>(json, converter);

The JSON object is quite complex and in order to find the value I am doing this spaghetti code:

KeyValuePair<string, object> CountPair = new KeyValuePair<string, object>();

var currentCountPair = jsonObject.FirstOrDefault(x => x.Key == "data").Value;

if (currentCountPair != null)
{
    currentCountPair = ((ExpandoObject)currentCountPair).FirstOrDefault(x => x.Key == "children");

    if (currentCountPair != null)
    {
        currentCountPair = ((IList<object>)((KeyValuePair<string, object>)currentCountPair).Value).FirstOrDefault();

        if (currentCountPair != null)
        {                                            
            currentCountPair = ((ExpandoObject)currentCountPair).FirstOrDefault(x => x.Key == "data").Value;

            if (currentCountPair != null)
            {
                CountPair = ((ExpandoObject)currentCountPair).FirstOrDefault(x => x.Key == "score");
                result.Score = CountPair.Value == null ? null : CountPair.Value.ToString();
            }
        }
    }

}

What do you think? Is there any better way of getting that value from ExpandoObject? Or at least to reduce the number of if statements and code complexity...

\$\endgroup\$
3
\$\begingroup\$

First, I don't see any reason for using ExpandoObject in your code. The same result could be achieved with cleaner code using JObjects.

Second, what would help if you could express your query using “LINQ to null”. Basically, treat null as an empty collection and any other value as a collection containing just that value. (The technical term for this is the Maybe monad.)

With both of these changes, your code could look something like this:

var jsonObject = JsonConvert.DeserializeObject<JObject>(json);

string result =
    from o in jsonObject.ToNullWrapper()
    from data in o["data"]
    from children in data["children"]
    from child in children.FirstOrDefault()
    from data2 in child["data"]
    from score in data2["score"]
    select score.ToString();

Note that (ab)using LINQ like this is not idiomatic and probably very confusing. But I think it results in very clean code.

To make this work, you need the implementation of LINQ to null:

public struct NullWrapper<T>
    where T : class
{
    public T Value { get; private set; }

    public NullWrapper(T value)
        : this()
    {
        Value = value;
    }

    public static implicit operator T(NullWrapper<T> wrapper)
    {
        return wrapper.Value;
    }
}

public static class NullExtensions
{
    public static NullWrapper<T> ToNullWrapper<T>(this T value)
        where T : class
    {
        return new NullWrapper<T>(value);
    }

    public static NullWrapper<TResult>
        SelectMany<TSource, TIntermediary, TResult>(
        this NullWrapper<TSource> source,
        Func<TSource, TIntermediary> intermediarySelector,
        Func<TSource, TIntermediary, TResult> resultSelector)
        where TSource : class
        where TIntermediary : class
        where TResult : class
    {
        if (source.Value == null)
            return new NullWrapper<TResult>();

        var intermediary = intermediarySelector(source.Value);

        if (intermediary == null)
            return new NullWrapper<TResult>();

        return resultSelector(source.Value, intermediary).ToNullWrapper();
    }
}
\$\endgroup\$
  • \$\begingroup\$ Note that I think that the SelectToken() approach from my other answer is a much better solution in this case. But I thought I'll leave this here anyway. \$\endgroup\$ – svick Apr 19 '14 at 16:36
2
\$\begingroup\$

Instead of ExpandoObject, you can use JObject together with its SelectToken() method:

var jsonObject = JsonConvert.DeserializeObject<JObject>(json);

var scoreToken = jsonObject.SelectToken("data.children[0].data.score");

string score = scoreToken == null ? null : scoreToken.ToString();
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.