ASP.NET Web API JSON 및 XML Serialization
이 문서에서는 ASP.NET Web API JSON 및 XML 포맷터에 대해 설명합니다.
ASP.NET Web API 미디어 형식 포맷터는 다음을 수행할 수 있는 개체입니다.
- HTTP 메시지 본문에서 CLR 개체 읽기
- HTTP 메시지 본문에 CLR 개체 쓰기
Web API는 JSON 및 XML 모두에 대한 미디어 형식 포맷터를 제공합니다. 프레임워크는 기본적으로 이러한 포맷터를 파이프라인에 삽입합니다. 클라이언트는 HTTP 요청의 Accept 헤더에서 JSON 또는 XML을 요청할 수 있습니다.
콘텐츠
JSON Media-Type 포맷터
JSON 형식 지정은 JsonMediaTypeFormatter 클래스에서 제공됩니다. 기본적으로 JsonMediaTypeFormatter 는 Json.NET 라이브러리를 사용하여 serialization을 수행합니다. Json.NET 타사 오픈 소스 프로젝트입니다.
원하는 경우 Json.NET 대신 DataContractJsonSerializer를 사용하도록 JsonMediaTypeFormatter 클래스를 구성할 수 있습니다. 이렇게 하려면 UseDataContractJsonSerializer 속성을 true로 설정합니다.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
JSON serialization
이 섹션에서는 기본 Json.NET 직렬 변환기를 사용하여 JSON 포맷터의 몇 가지 특정 동작에 대해 설명합니다. 이는 Json.NET 라이브러리에 대한 포괄적인 설명서가 아닙니다. 자세한 내용은 Json.NET 설명서를 참조하세요.
Serialize되는 항목은 무엇인가요?
기본적으로 모든 공용 속성 및 필드는 직렬화된 JSON에 포함됩니다. 속성 또는 필드를 생략하려면 JsonIgnore 특성으로 데코레이트합니다.
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
}
"옵트인" 방법을 선호하는 경우 DataContract 특성으로 클래스를 데코레이트합니다. 이 특성이 있는 경우 멤버는 DataMember가 없는 한 무시됩니다. DataMember를 사용하여 프라이빗 멤버를 직렬화할 수도 있습니다.
[DataContract]
public class Product
{
[DataMember]
public string Name { get; set; }
[DataMember]
public decimal Price { get; set; }
public int ProductCode { get; set; } // omitted by default
}
Read-Only 속성
읽기 전용 속성은 기본적으로 직렬화됩니다.
날짜
기본적으로 Json.NET ISO 8601 형식으로 날짜를 씁니다. UTC(협정 세계시)의 날짜는 "Z" 접미사로 작성됩니다. 현지 시간의 날짜에는 표준 시간대 오프셋이 포함됩니다. 예:
2012-07-27T18:51:45.53403Z // UTC
2012-07-27T11:51:45.53403-07:00 // Local
기본적으로 Json.NET 표준 시간대를 유지합니다. DateTimeZoneHandling 속성을 설정하여 이를 재정의할 수 있습니다.
// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
ISO 8601 대신 Microsoft JSON 날짜 형식 ("\/Date(ticks)\/"
)을 사용하려면 serializer 설정에서 DateFormatHandling 속성을 설정합니다.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
들여쓰기
들여쓰기된 JSON을 작성하려면 서식 설정을 Formatting.Indented로 설정합니다.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
낙타 대/소문자
데이터 모델을 변경하지 않고 카멜 대/소문자를 사용하여 JSON 속성 이름을 작성하려면 serializer에서 CamelCasePropertyNamesContractResolver 를 설정합니다.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
익명 및 Weakly-Typed 개체
작업 메서드는 익명 개체를 반환하고 JSON으로 serialize할 수 있습니다. 예:
public object Get()
{
return new {
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}
응답 메시지 본문에는 다음 JSON이 포함됩니다.
{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
웹 API가 클라이언트에서 느슨하게 구조화된 JSON 개체를 수신하는 경우 요청 본문을 Newtonsoft.Json.Linq.JObject 형식으로 역직렬화할 수 있습니다.
public void Post(JObject person)
{
string name = person["Name"].ToString();
int age = person["Age"].ToObject<int>();
}
그러나 일반적으로 강력한 형식의 데이터 개체를 사용하는 것이 좋습니다. 그런 다음 직접 데이터를 구문 분석할 필요가 없으며 모델 유효성 검사의 이점을 얻을 수 있습니다.
XML serializer는 익명 형식 또는 JObject 인스턴스를 지원하지 않습니다. JSON 데이터에 이러한 기능을 사용하는 경우 이 문서의 뒷부분에 설명된 대로 파이프라인에서 XML 포맷터를 제거해야 합니다.
XML Media-Type 포맷터
XML 서식 지정은 XmlMediaTypeFormatter 클래스에서 제공됩니다. 기본적으로 XmlMediaTypeFormatter 는 DataContractSerializer 클래스를 사용하여 serialization을 수행합니다.
원하는 경우 DataContractSerializer 대신 XmlSerializer를 사용하도록 XmlMediaTypeFormatter를 구성할 수 있습니다. 이렇게 하려면 UseXmlSerializer 속성을 true로 설정합니다.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
XmlSerializer 클래스는 DataContractSerializer보다 더 좁은 형식 집합을 지원하지만 결과 XML을 더 많이 제어합니다. 기존 XML 스키마와 일치해야 하는 경우 XmlSerializer 를 사용하는 것이 좋습니다.
XML Serialization
이 섹션에서는 기본 DataContractSerializer를 사용하는 XML 포맷터의 몇 가지 특정 동작에 대해 설명합니다.
기본적으로 DataContractSerializer는 다음과 같이 작동합니다.
- 모든 공용 읽기/쓰기 속성 및 필드가 serialize됩니다. 속성 또는 필드를 생략하려면 IgnoreDataMember 특성으로 데코레이트합니다.
- 프라이빗 및 보호된 멤버는 직렬화되지 않습니다.
- 읽기 전용 속성은 serialize되지 않습니다. (그러나 읽기 전용 컬렉션 속성의 내용은 serialize됩니다.)
- 클래스 및 멤버 이름은 클래스 선언에 표시되는 것과 똑같이 XML로 작성됩니다.
- 기본 XML 네임스페이스가 사용됩니다.
serialization에 대한 더 많은 제어가 필요한 경우 DataContract 특성으로 클래스를 데코레이트할 수 있습니다. 이 특성이 있으면 클래스는 다음과 같이 직렬화됩니다.
- "옵트인" 접근 방식: 속성 및 필드는 기본적으로 직렬화되지 않습니다. 속성 또는 필드를 serialize하려면 DataMember 특성으로 데코레이트합니다.
- 프라이빗 또는 보호된 멤버를 직렬화하려면 DataMember 특성으로 데코레이트합니다.
- 읽기 전용 속성은 serialize되지 않습니다.
- 클래스 이름이 XML에 표시되는 방식을 변경하려면 DataContract 특성에서 Name 매개 변수를 설정합니다.
- XML에 멤버 이름이 표시되는 방식을 변경하려면 DataMember 특성에서 Name 매개 변수를 설정합니다.
- XML 네임스페이스를 변경하려면 DataContract 클래스에서 네임스페이스 매개 변수를 설정합니다.
Read-Only 속성
읽기 전용 속성은 serialize되지 않습니다. 읽기 전용 속성에 지원 프라이빗 필드가 있는 경우 DataMember 특성으로 프라이빗 필드를 표시할 수 있습니다. 이 방법을 사용하려면 클래스에 DataContract 특성이 필요합니다.
[DataContract]
public class Product
{
[DataMember]
private int pcode; // serialized
// Not serialized (read-only)
public int ProductCode { get { return pcode; } }
}
날짜
날짜는 ISO 8601 형식으로 작성됩니다. 예: "2012-05-23T20:21:37.9116538Z".
들여쓰기
들여쓰기된 XML을 작성하려면 Indent 속성을 true로 설정합니다.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
Per-Type XML 직렬 변환기 설정
다양한 CLR 형식에 대해 다른 XML 직렬 변환기를 설정할 수 있습니다. 예를 들어 이전 버전과의 호환성을 위해 XmlSerializer 가 필요한 특정 데이터 개체가 있을 수 있습니다. 이 개체 에 XmlSerializer 를 사용하고 다른 형식에 DataContractSerializer 를 계속 사용할 수 있습니다.
특정 형식에 대한 XML serializer를 설정하려면 SetSerializer를 호출합니다.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
XmlSerializer 또는 XmlObjectSerializer에서 파생된 개체를 지정할 수 있습니다.
JSON 또는 XML 포맷터 제거
사용하지 않으려면 포맷터 목록에서 JSON 포맷터 또는 XML 포맷터를 제거할 수 있습니다. 이 작업을 수행하는 기본 이유는 다음과 같습니다.
- 웹 API 응답을 특정 미디어 형식으로 제한합니다. 예를 들어 JSON 응답만 지원하고 XML 포맷터를 제거하도록 결정할 수 있습니다.
- 기본 포맷터를 사용자 지정 포맷터로 바꾸려면 예를 들어 JSON 포맷터를 JSON 포맷터의 사용자 지정 구현으로 바꿀 수 있습니다.
다음 코드에서는 기본 포맷터를 제거하는 방법을 보여 줍니다. Global.asax에 정의된 Application_Start 메서드에서 호출합니다.
void ConfigureApi(HttpConfiguration config)
{
// Remove the JSON formatter
config.Formatters.Remove(config.Formatters.JsonFormatter);
// or
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
순환 개체 참조 처리
기본적으로 JSON 및 XML 포맷터는 모든 개체를 값으로 씁니다. 두 속성이 동일한 개체를 참조하거나 동일한 개체가 컬렉션에 두 번 표시되는 경우 포맷터는 개체를 두 번 직렬화합니다. 직렬 변환기가 그래프에서 루프를 검색할 때 예외를 throw하기 때문에 개체 그래프에 주기가 포함된 경우 이는 특히 문제가 됩니다.
다음 개체 모델 및 컨트롤러를 고려합니다.
public class Employee
{
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
public class DepartmentsController : ApiController
{
public Department Get(int id)
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return sales;
}
}
이 작업을 호출하면 포맷터가 예외를 throw합니다. 이 예외는 클라이언트에 대한 상태 코드 500(내부 서버 오류) 응답으로 변환됩니다.
JSON에서 개체 참조를 유지하려면 Global.asax 파일의 Application_Start 메서드에 다음 코드를 추가합니다.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
이제 컨트롤러 작업은 다음과 같은 JSON을 반환합니다.
{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
serializer는 두 개체에 "$id" 속성을 추가합니다. 또한 Employee.Department 속성이 루프를 만드는 것을 감지하여 값을 개체 참조 {"$ref":"1"}로 바꿉니다.
참고
개체 참조는 JSON에서 표준이 아닙니다. 이 기능을 사용하기 전에 클라이언트가 결과를 구문 분석할 수 있는지 여부를 고려합니다. 단순히 그래프에서 주기를 제거하는 것이 더 좋을 수 있습니다. 예를 들어 이 예제에서는 Employee에서 부서로의 링크가 실제로 필요하지 않습니다.
XML에서 개체 참조를 유지하려면 두 가지 옵션이 있습니다. 더 간단한 옵션은 모델 클래스에 추가하는 [DataContract(IsReference=true)]
것입니다. IsReference 매개 변수는 개체 참조를 사용하도록 설정합니다. DataContract는 serialization 옵트인을 수행하므로 속성에 DataMember 특성도 추가해야 합니다.
[DataContract(IsReference=true)]
public class Department
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Employee Manager { get; set; }
}
이제 포맷터는 다음과 유사한 XML을 생성합니다.
<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/Models">
<Manager>
<Department z:Ref="i1" />
<Name>Alice</Name>
</Manager>
<Name>Sales</Name>
</Department>
모델 클래스의 특성을 방지하려면 다른 옵션이 있습니다. 새 형식별 DataContractSerializer instance 만들고 생성자에서 preserveObjectReferences를 true로 설정합니다. 그런 다음 이 instance XML 미디어 형식 포맷터에서 형식별 직렬 변환기로 설정합니다. 다음 코드는 이 작업을 수행하는 방법을 보여줍니다.
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
개체 직렬화 테스트
웹 API를 디자인할 때 데이터 개체를 직렬화하는 방법을 테스트하는 것이 유용합니다. 컨트롤러를 만들거나 컨트롤러 작업을 호출하지 않고 이 작업을 수행할 수 있습니다.
string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
// Write the serialized string to a memory stream.
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
// Deserialize to an object of type T
return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}
// Example of use
void TestSerialization()
{
var value = new Person() { Name = "Alice", Age = 23 };
var xml = new XmlMediaTypeFormatter();
string str = Serialize(xml, value);
var json = new JsonMediaTypeFormatter();
str = Serialize(json, value);
// Round trip
Person person2 = Deserialize<Person>(json, str);
}