asp.net mvcのJsonResultを介して返されたExpandoObjectをフラット化する方法は?


95

ExpandoObject実行時にサーバー側の動的オブジェクトをコンパイルしている間は本当に気に入っていますが、JSONのシリアル化中にこれをフラット化するのに問題があります。まず、オブジェクトをインスタンス化します。

dynamic expando = new ExpandoObject();
var d = expando as IDictionary<string, object>;
expando.Add("SomeProp", SomeValueOrClass);

ここまでは順調ですね。私のMVCコントローラーでは、これをJsonResultとして送信したいので、次のようにします。

return new JsonResult(expando);

これにより、JSONが以下のようにシリアル化され、ブラウザーで使用されます。

[{"Key":"SomeProp", "Value": SomeValueOrClass}]

しかし、私が本当に望んでいるのはこれを見ることです:

{SomeProp: SomeValueOrClass}

dynamic代わりに使用すると、これを達成できることを知っていますExpandoObject- プロパティと値を単一のオブジェクトJsonResultにシリアル化できdynamicます(KeyまたはValueビジネスはありません)が、使用する必要がある理由ExpandoObjectは、実行時までオブジェクトに必要なプロパティ、および私の知る限り、dynamicを使用しないとプロパティをに動的に追加できませんExpandoObject

私はJavaScriptで「キー」、「バリュー」ビジネスをふるいにかける必要があるかもしれませんが、クライアントに送信する前にこれを理解することを望んでいました。ご協力いただきありがとうございます!


9
ExpandoObjectではなく、Dictionary <string、object>を使用しないのはなぜですか?それはあなたが望むフォーマットに自動的にシリアライズします、そしてあなたはとにかく辞書のようにあなたのExpandoObjectを使うだけです。正当なExpandoObjectをシリアル化する場合は、「return new JsonResult(d.ToDictionary(x => x.Key、x => x.Value));」を使用します。アプローチはおそらく最良の妥協です。
BrainSlugs83 2012年

回答:


36

また、ExpandoObjectに対してのみ機能する特別なJSONConverterを作成し、それをJavaScriptSerializerのインスタンスに登録することもできます。このようにして、expandoの配列、expandoオブジェクトの組み合わせなどをシリアル化して、正しくシリアル化されていない別の種類のオブジェクトが見つかるまで( "方法どおりに")、別のコンバーターを作成するか、別の型を追加します。これです。お役に立てれば。

using System.Web.Script.Serialization;    
public class ExpandoJSONConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {         
        var result = new Dictionary<string, object>();
        var dictionary = obj as IDictionary<string, object>;
        foreach (var item in dictionary)
            result.Add(item.Key, item.Value);
        return result;
    }
    public override IEnumerable<Type> SupportedTypes
    {
        get 
        { 
              return new ReadOnlyCollection<Type>(new Type[] { typeof(System.Dynamic.ExpandoObject) });
        }
    }
}

コンバーターの使用

var serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoJSONConverter()});
var json = serializer.Serialize(obj);

2
これは私のニーズに最適でした。誰かがのNotImplementedExceptionようなものを追加するためにいくつかのコードをプラグインしたい場合serializer.Deserialize<ExpandoObject>(json);、@ theburningmonk 私のために働いたソリューション提供します。
パトリッジ'22

2
カスタムシリアライゼーションルーチンをMVCフレームワークにプラグインする@ pablo.Excellentの優れた例!
pb。

私がこれを行うのに見つけた最も簡単な方法は、次のとおりです。どう思いますか?
カヴァイン

シリアライザが再帰的に呼び出されます。RecursionLimitを設定すると、再帰制限超過エラーまたはスタックオーバーフロー例外エラーが発生します。私は何をすべきか?:(
ダナシュリー2017

71

JSON.NETを使用してSerializeObjectを呼び出し、expandoオブジェクトを「フラット化」できます。

dynamic expando = new ExpandoObject();
expando.name = "John Smith";
expando.age = 30;

var json = JsonConvert.SerializeObject(expando);

出力されます:

{"name":"John Smith","age":30}

ASP.NET MVCコントローラーのコンテキストでは、Contentメソッドを使用して結果を返すことができます。

public class JsonController : Controller
{
    public ActionResult Data()
    {
        dynamic expando = new ExpandoObject();
        expando.name = "John Smith";
        expando.age = 30;

        var json = JsonConvert.SerializeObject(expando);

        return Content(json, "application/json");
    }
}

1
Newtonsoft.Json?
アヤッシュ2013

3
newtonsoft.jsonは、expandosまたはディクショナリおよび内部ディクショナリ内の再帰的なエキスパンドの処理が改善されています
Jone Polvora

26

これが、あなたが説明している動作を実現するために私がしたことです。

dynamic expando = new ExpandoObject();
expando.Blah = 42;
expando.Foo = "test";
...

var d = expando as IDictionary<string, object>;
d.Add("SomeProp", SomeValueOrClass);

// After you've added the properties you would like.
d = d.ToDictionary(x => x.Key, x => x.Value);
return new JsonResult(d);

コストは、シリアル化する前にデータのコピーを作成することです。


いいね。また、動的に動的にキャストすることもできます。returnnew JsonResult(((ExpandoObject)someIncomingDynamicExpando).ToDictionary(item => item.Key、item => item.Value))
joeriks

「expando.Add」が機能しません。この場合、それは「d.Add」(私にとってはうまくいった)だと思います。
ジャスティン

9
待って... ExpandoObjectを作成し、それを辞書としてキャストし、それを辞書のように使用し、それが十分でない場合は、辞書に変換します... ...で辞書を使用しないのはなぜですかこの場合?... o_o
BrainSlugs83

5
アンはExpandoObjectあなたに簡単な辞書よりもはるかに柔軟性を提供します。上記の例はそれを示していませんが、の動的機能を使用ExpandoObjectして、JSONに必要なプロパティを追加できます。通常のDictioanryオブジェクトは問題なくJSONに変換されるので、変換を行うことで、使いやすいダイナミックExpandoObjectをJSON化できる形式にする簡単なO(n)の方法です。あなたは正しいですが、上記の例はのレディクル使用ですExpandoObject。シンプルなDictionary方がはるかに良いでしょう。
AJB

1
同様にそのアプローチより-どのような環境では動作しませんコピーを作成するが、私は....唯一の小さなオブジェクトを持っているとはExpandoが(不変)サードパーティから提供された
セバスチャンJ.

12

ExpandoObjectをJSON文字列に変換する拡張メソッドを記述することでこれを解決しました。

public static string Flatten(this ExpandoObject expando)
{
    StringBuilder sb = new StringBuilder();
    List<string> contents = new List<string>();
    var d = expando as IDictionary<string, object>;
    sb.Append("{");

    foreach (KeyValuePair<string, object> kvp in d) {
        contents.Add(String.Format("{0}: {1}", kvp.Key,
           JsonConvert.SerializeObject(kvp.Value)));
    }
    sb.Append(String.Join(",", contents.ToArray()));

    sb.Append("}");

    return sb.ToString();
}

これは優れたNewtonsoftライブラリを使用します。

JsonResultは次のようになります。

return JsonResult(expando.Flatten());

そして、これはブラウザに返されます:

"{SomeProp: SomeValueOrClass}"

そして私はこれを行うことでJavaScriptでそれを使うことができます(ここで参照されています):

var obj = JSON.parse(myJsonString);

これが役に立てば幸いです!


7
評価しないでください!セキュリティの問題を回避するには、JSONデシリアライザを使用する必要があります。json2.jsを参照してください:json.org/js.html var o = JSON.parse(myJsonString);
ランスフィッシャー

私はその拡張メソッドが好きです。いいね!
ランスフィッシャー

3
-1:文字列を返す拡張メソッドでこれを行うことは、この動作をフレームワークとインターフェースする正しい方法ではありません。代わりに、組み込みのシリアル化アーキテクチャを拡張する必要があります。
BrainSlugs83 2012年

1
このメソッドの主な欠点は、再帰の欠如です。最上位のオブジェクトが動的であることがわかっていればそれで機能しますが、動的オブジェクトがオブジェクトツリーの任意のレベルまたはすべてのレベルで返される可能性がある場合、これは失敗します。
Chris Moschini、2013年

このメソッドを再帰的にするために、いくつかの改良を加えました。これがコードです:gist.github.com/renanvieira/e26dc34e2de156723f79
MaltMaster

5

JsonFxを使用してこれと同じ問題を解決することができました。

        dynamic person = new System.Dynamic.ExpandoObject();
        person.FirstName  = "John";
        person.LastName   = "Doe";
        person.Address    = "1234 Home St";
        person.City       = "Home Town";
        person.State      = "CA";
        person.Zip        = "12345";

        var writer = new JsonFx.Json.JsonWriter();
        return writer.Write(person);

出力:

{"FirstName": "John"、 "LastName": "Doe"、 "Address": "1234 Home St"、 "City": "Home Town"、 "State": "CA"、 "Zip": "12345 "}


1
次の手順を実行して、JSON .Net(Newtonsoft)を使用してこれを行うこともできます。var entity = person as object; var json = JsonConvert.SerializeObject(entity);
bkorzynski 2013

4

フラット化プロセスをさらに一歩進め、リストオブジェクトをチェックしました。これにより、意味のないキー値が削除されます。:)

public string Flatten(ExpandoObject expando)
    {
        StringBuilder sb = new StringBuilder();
        List<string> contents = new List<string>();
        var d = expando as IDictionary<string, object>;
        sb.Append("{ ");

        foreach (KeyValuePair<string, object> kvp in d)
        {       
            if (kvp.Value is ExpandoObject)
            {
                ExpandoObject expandoValue = (ExpandoObject)kvp.Value;
                StringBuilder expandoBuilder = new StringBuilder();
                expandoBuilder.Append(String.Format("\"{0}\":[", kvp.Key));

                String flat = Flatten(expandoValue);
                expandoBuilder.Append(flat);

                string expandoResult = expandoBuilder.ToString();
                // expandoResult = expandoResult.Remove(expandoResult.Length - 1);
                expandoResult += "]";
                contents.Add(expandoResult);
            }
            else if (kvp.Value is List<Object>)
            {
                List<Object> valueList = (List<Object>)kvp.Value;

                StringBuilder listBuilder = new StringBuilder();
                listBuilder.Append(String.Format("\"{0}\":[", kvp.Key));
                foreach (Object item in valueList)
                {
                    if (item is ExpandoObject)
                    {
                        String flat = Flatten(item as ExpandoObject);
                        listBuilder.Append(flat + ",");
                    }
                }

                string listResult = listBuilder.ToString();
                listResult = listResult.Remove(listResult.Length - 1);
                listResult += "]";
                contents.Add(listResult);

            }
            else
            { 
                contents.Add(String.Format("\"{0}\": {1}", kvp.Key,
                   JsonSerializer.Serialize(kvp.Value)));
            }
            //contents.Add("type: " + valueType);
        }
        sb.Append(String.Join(",", contents.ToArray()));

        sb.Append("}");

        return sb.ToString();
    }

3

これは役に立たないかもしれませんが、同様の要件がありましたが、SerializableDynamicObjectを使用しました

辞書の名前を「Fields」に変更すると、Json.Netでシリアル化され、次のようなjsonが生成されます。

{"Fields":{"Property1": "Value1"、 "Property2": "Value2"など。Property1とProperty2は動的に追加されるプロパティ、つまり辞書キーです。

残りをカプセル化する余分な "Fields"プロパティを取り除くことができれば完璧ですが、私はその制限を回避しました。

リクエストに応じて、この質問から回答を移動しました


3

これは遅い回答ですが、同じ問題があり、この質問は私がそれらを解決するのに役立ちました。まとめとして、他の人の実装をスピードアップすることを期待して、自分の結果を投稿するべきだと思いました。

最初に、アクションでのインスタンスを返すことができるExpandoJsonResult。または、コントローラーのJsonメソッドをオーバーライドして、そこに返すこともできます。

public class ExpandoJsonResult : JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
        response.ContentEncoding = ContentEncoding ?? response.ContentEncoding;

        if (Data != null)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoConverter() });
            response.Write(serializer.Serialize(Data));
        }
    }
}

次に、コンバーター(シリアル化と非シリアル化の両方をサポートします。逆シリアル化する方法の例については、以下を参照してください)。

public class ExpandoConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    { return DictionaryToExpando(dictionary); }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    { return ((ExpandoObject)obj).ToDictionary(x => x.Key, x => x.Value); }

    public override IEnumerable<Type> SupportedTypes
    { get { return new ReadOnlyCollection<Type>(new Type[] { typeof(System.Dynamic.ExpandoObject) }); } }

    private ExpandoObject DictionaryToExpando(IDictionary<string, object> source)
    {
        var expandoObject = new ExpandoObject();
        var expandoDictionary = (IDictionary<string, object>)expandoObject;
        foreach (var kvp in source)
        {
            if (kvp.Value is IDictionary<string, object>) expandoDictionary.Add(kvp.Key, DictionaryToExpando((IDictionary<string, object>)kvp.Value));
            else if (kvp.Value is ICollection)
            {
                var valueList = new List<object>();
                foreach (var value in (ICollection)kvp.Value)
                {
                    if (value is IDictionary<string, object>) valueList.Add(DictionaryToExpando((IDictionary<string, object>)value));
                    else valueList.Add(value);
                }
                expandoDictionary.Add(kvp.Key, valueList);
            }
            else expandoDictionary.Add(kvp.Key, kvp.Value);
        }
        return expandoObject;
    }
}

ExpandoJsonResultクラスで、シリアル化にそれを使用する方法を確認できます。逆シリアル化するには、同じ方法でシリアライザを作成し、コンバータを登録しますが、

dynamic _data = serializer.Deserialize<ExpandoObject>("Your JSON string");

私を助けてくれた参加者の皆さん、本当にありがとうございました。


1

ASP.Net 4のWebApiから動的ExpandoObjectを返すことを使用して、デフォルトのJSONフォーマッターはExpandoObjectsを単純なJSONオブジェクトにフラット化するようです。


1

JsonResultあなたが望むようJavaScriptSerializerに実際に(コンクリート)デシリアライズする使用Dictionary<string, object>

Dictionary<string, object>をとるコンストラクタのオーバーロードがありますIDictionary<string, object>

ExpandoObject道具IDictionary<string, object> (私がここに行くところがわかると思います...)

単一レベルのExpandoObject

dynamic expando = new ExpandoObject();

expando.hello = "hi";
expando.goodbye = "cya";

var dictionary = new Dictionary<string, object>(expando);

return this.Json(dictionary); // or new JsonResult { Data = dictionary };

すべての組み込み型を使用する1行のコード:)

ネストされたExpandoObjects

もちろん、ExpandoObjects をネストしている場合は、それらをすべてDictionary<string, object>s に再帰的に変換する必要があります。

public static Dictionary<string, object> RecursivelyDictionary(
    IDictionary<string, object> dictionary)
{
    var concrete = new Dictionary<string, object>();

    foreach (var element in dictionary)
    {
        var cast = element.Value as IDictionary<string, object>;
        var value = cast == null ? element.Value : RecursivelyDictionary(cast);
        concrete.Add(element.Key, value);
    }

    return concrete;
}

最終的なコードになる

dynamic expando = new ExpandoObject();
expando.hello = "hi";
expando.goodbye = "cya";
expando.world = new ExpandoObject();
expando.world.hello = "hello world";

var dictionary = RecursivelyDictionary(expando);

return this.Json(dictionary);

-2

シリアライザはExpandoをディクショナリにキャストしてからシリアル化しているようです(したがって、キー/バリュービジネス)。辞書としてデシリアライズしてからExpandoにキャストしてみましたか?


1
ExpandoオブジェクトはIDictionary <string、object>を実装しているので、JsonResultがそれをキー/値ペアの配列にシリアル化するのはそのためだと思います。それをIDictionaryとしてキャストし、再び戻すことは、それをフラット化するのに実際には役立たないと思います。
TimDog 2011年

-2

私は同じ問題を抱えていて、かなり奇妙なものを見つけました。私が行った場合:

dynamic x = new ExpandoObject();
x.Prop1 = "xxx";
x.Prop2 = "yyy";
return Json
(
    new
    {
        x.Prop1,
        x.Prop2
    }
);

それは機能しますが、私のメソッドがHttpPost属性を使用する場合のみです。HttpGetを使用すると、エラーが発生します。したがって、私の回答はHttpPostでのみ機能します。私の場合はAjax呼び出しだったので、HttpPostによってHttpGetを変更できました。


2
-1これは、stackoverflow.com / a / 7042631/11635に要約されるため、あまり有用ではありません。また、向きを変えて静的に名前に依存する場合は、これを動的に実行しても意味がありません。AllowGetの問題は完全に直交しています。
Ruben Bartelink 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.