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(아무 키 또는 값 사업과) 하나의 객체에 속성과 값을, 하지만 난 사용을 필요로하는 이유는 ExpandoObject내가 모두를 알 수 없기 때문에입니다 내가 아는 한 런타임까지 객체에 원하는 속성을 dynamic사용하지 않고 는 동적으로 속성을 추가 할 수 없습니다 ExpandoObject.

내 자바 스크립트에서 "키", "가치"비즈니스를 살펴 봐야 할 수도 있지만 클라이언트에게 보내기 전에이를 파악하고 싶었습니다. 당신의 도움을 주셔서 감사합니다!


9
ExpandoObject 대신 Dictionary <string, object>를 사용하지 않는 이유는 무엇입니까? 원하는 형식으로 자동으로 직렬화되며 어쨌든 사전처럼 ExpandoObject 만 사용하고 있습니다. 합법적 인 ExpandoObject를 직렬화하려면 "return new JsonResult (d.ToDictionary (x => x.Key, x => x.Value));"를 사용하여 접근 방식이 아마도 최선의 타협 일 것입니다.
BrainSlugs83

답변:


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 가 저에게 적합한 솔루션제공합니다 .
patridge

2
@pablo. 사용자 정의 직렬화 루틴을 MVC 프레임 워크에 연결하는 훌륭한 예입니다!
pb.

이 작업을 수행하는 가장 쉬운 방법은 다음과 같습니다. new JavaScriptSerializer (). Deserialize <object> (Newtonsoft.Json.JsonConvert.SerializeObject (listOfExpandoObject)); 어떻게 생각해?
kavain

내 직렬 변환기가 재귀 적으로 호출됩니다. RecursionLimit를 설정하면 재귀 제한 초과 오류 또는 스택 오버플로 예외 오류가 발생합니다. 어떻게해야합니까? :(
Dhanashree jul.

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-method를 사용하여 반환 될 수 있습니다.

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 의미?
Ayyash 2013 년

3
newtonsoft.json 더 expandos 또는 사전 및 상자의 내부 사전, 내부 순환 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);

비용은 직렬화하기 전에 데이터의 복사본을 만드는 것입니다.


좋은. 또한 실시간으로 동적을 캐스팅 할 수 있습니다. return new JsonResult (((ExpandoObject) someIncomingDynamicExpando) .ToDictionary (item => item.Key, item => item.Value))
joeriks

"expando.Add"가 작동하지 않습니다. 나는이 경우 "d.Add"라고 믿습니다.
Justin

9
잠깐 ... ExpandoObject를 만들고, 사전으로 캐스팅하고, 사전처럼 사용하고, 충분하지 않을 때 사전으로 변환합니다 ... ... 이 경우? ... O_O
BrainSlugs83

5
An ExpandoObject은 단순한 사전보다 훨씬 더 많은 유연성을 제공합니다. 위의 예에서는이를 설명하지 않지만의 동적 기능을 사용 ExpandoObject하여 JSON에 포함 할 속성을 추가 할 수 있습니다 . 일반 Dictioanry객체는 문제없이 JSON으로 변환되므로 변환을 수행하면 사용하기 쉬운 동적 ExpandoObject을 JSON화할 수있는 형식 에 넣는 간단한 O (n) 방법 입니다. 그래도 맞습니다. 위의 예는 ExpandoObject; 단순한 Dictionary것이 훨씬 낫습니다.
ajb

1
그 접근 방식과 비슷합니다. 복사본을 만드는 것은 어떤 환경에서도 작동하지 않지만 작은 개체 만 있고 Expando는 (변경할 수없는) 타사에서 제공합니다 ....
Sebastian 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}"

그리고 다음을 수행하여 자바 스크립트에서 사용할 수 있습니다 ( 여기 참조 ).

var obj = JSON.parse(myJsonString);

이게 도움이 되길 바란다!


7
그것을 평가하지 마십시오! 보안 문제를 방지하려면 JSON deserializer를 사용해야합니다. json2.js를 참조하십시오 : json.org/js.html var o = JSON.parse (myJsonString);
Lance Fisher

나는 그 확장 방법을 좋아합니다. 좋은!
Lance Fisher

3
-1 : 문자열을 반환하는 확장 메서드에서이 작업을 수행하는 것은이 동작을 프레임 워크와 인터페이스하는 올바른 방법이 아닙니다. 대신 기본 제공 직렬화 아키텍처를 확장해야합니다.
BrainSlugs83

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 = 개체로서의 사람; var json = JsonConvert.SerializeObject (entity);
bkorzynski

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

JsonResultJavaScriptSerializer실제로 deserialize하는 사용 (콘크리트)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 };

모든 내장 유형을 사용하는 한 줄의 코드 :)

중첩 된 ExpandoObject

물론 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

serializer가 Expando를 Dictionary로 캐스팅 한 다음 직렬화하는 것 같습니다 (따라서 Key / Value 비즈니스). 사전으로 역 직렬화를 시도한 다음 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.