JavaScriptSerializer.Deserialize-필드 이름을 변경하는 방법
요약 : JavaScriptSerializer.Deserialize를 사용할 때 JSON 데이터의 필드 이름을 .Net 개체의 필드 이름에 어떻게 매핑합니까?
더 긴 버전 : 서버 API에서 나에게 오는 다음 JSON 데이터가 있습니다 (.Net에서 코딩되지 않음)
{"user_id":1234, "detail_level":"low"}
다음과 같은 C # 개체가 있습니다.
[Serializable]
public class DataObject
{
[XmlElement("user_id")]
public int UserId { get; set; }
[XmlElement("detail_level")]
public DetailLevel DetailLevel { get; set; }
}
DetailLevel은 값 중 하나로 "Low"가있는 열거 형입니다.
이 테스트는 실패합니다.
[TestMethod]
public void DataObjectSimpleParseTest()
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
Assert.IsNotNull(dataObject);
Assert.AreEqual(DetailLevel.Low, dataObject.DetailLevel);
Assert.AreEqual(1234, dataObject.UserId);
}
마지막 두 개의 어설 션은 해당 필드에 데이터가 없기 때문에 실패합니다. JSON 데이터를 다음으로 변경하면
{"userid":1234, "detaillevel":"low"}
그런 다음 통과합니다. 그러나 서버의 동작을 변경할 수 없으며 클라이언트 클래스가 C # 관용구에서 잘 명명 된 속성을 갖기를 원합니다. Silverlight 외부에서 작동하기를 원하기 때문에 LINQ to JSON을 사용할 수 없습니다. XmlElement 태그가 효과가없는 것 같습니다. 나는 그들이 관련이 있다는 생각을 어디서 얻었는지 모르겠지만 아마 그렇지 않을 것입니다.
JavaScriptSerializer에서 필드 이름 매핑을 어떻게 수행합니까? 전혀 할 수 있습니까?
DataContractJsonSerializer 클래스를 사용하여 또 다른 시도를했습니다 . 이것은 그것을 해결합니다 :
코드는 다음과 같습니다.
using System.Runtime.Serialization;
[DataContract]
public class DataObject
{
[DataMember(Name = "user_id")]
public int UserId { get; set; }
[DataMember(Name = "detail_level")]
public string DetailLevel { get; set; }
}
그리고 테스트는 다음과 같습니다.
using System.Runtime.Serialization.Json;
[TestMethod]
public void DataObjectSimpleParseTest()
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DataObject));
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(JsonData));
DataObject dataObject = serializer.ReadObject(ms) as DataObject;
Assert.IsNotNull(dataObject);
Assert.AreEqual("low", dataObject.DetailLevel);
Assert.AreEqual(1234, dataObject.UserId);
}
유일한 단점은 DetailLevel을 열거 형에서 문자열로 변경해야한다는 것입니다. 열거 형 형식을 그대로 유지하면 DataContractJsonSerializer가 숫자 값을 읽을 것으로 예상하고 실패합니다. 자세한 내용은 DataContractJsonSerializer 및 Enums 를 참조 하십시오.
제 생각에는 이것은 특히 JavaScriptSerializer가 올바르게 처리하기 때문에 매우 가난합니다. 다음은 문자열을 열거 형으로 구문 분석하려는 경우 예외입니다.
System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type DataObject. The value 'low' cannot be parsed as the type 'Int64'. --->
System.Xml.XmlException: The value 'low' cannot be parsed as the type 'Int64'. --->
System.FormatException: Input string was not in a correct format
다음과 같이 열거 형을 표시해도이 동작은 변경되지 않습니다.
[DataContract]
public enum DetailLevel
{
[EnumMember(Value = "low")]
Low,
...
}
이것은 Silverlight에서도 작동하는 것 같습니다.
사용자 정의 JavaScriptConverter 를 생성하여 모든 이름을 모든 속성에 매핑 할 수 있습니다. 그러나지도를 직접 코딩해야하는데 이는 이상적이지 않습니다.
public class DataObjectJavaScriptConverter : JavaScriptConverter
{
private static readonly Type[] _supportedTypes = new[]
{
typeof( DataObject )
};
public override IEnumerable<Type> SupportedTypes
{
get { return _supportedTypes; }
}
public override object Deserialize( IDictionary<string, object> dictionary,
Type type,
JavaScriptSerializer serializer )
{
if( type == typeof( DataObject ) )
{
var obj = new DataObject();
if( dictionary.ContainsKey( "user_id" ) )
obj.UserId = serializer.ConvertToType<int>(
dictionary["user_id"] );
if( dictionary.ContainsKey( "detail_level" ) )
obj.DetailLevel = serializer.ConvertToType<DetailLevel>(
dictionary["detail_level"] );
return obj;
}
return null;
}
public override IDictionary<string, object> Serialize(
object obj,
JavaScriptSerializer serializer )
{
var dataObj = obj as DataObject;
if( dataObj != null )
{
return new Dictionary<string,object>
{
{"user_id", dataObj.UserId },
{"detail_level", dataObj.DetailLevel }
}
}
return new Dictionary<string, object>();
}
}
그런 다음 다음과 같이 역 직렬화 할 수 있습니다.
var serializer = new JavaScriptSerializer();
serialzer.RegisterConverters( new[]{ new DataObjectJavaScriptConverter() } );
var dataObj = serializer.Deserialize<DataObject>( json );
Json.NET 은 원하는 것을 수행합니다 (면책 조항 : 패키지 작성자입니다). DataContract / DataMember 속성 읽기와 속성 이름 변경을 지원합니다. 또한 열거 형 값을 숫자가 아닌 이름으로 직렬화하기위한 StringEnumConverter 클래스가 있습니다.
속성 이름 변경에 대한 표준 지원은 없지만 JavaScriptSerializer
자신 만의 속성을 쉽게 추가 할 수 있습니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;
using System.Reflection;
public class JsonConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
List<MemberInfo> members = new List<MemberInfo>();
members.AddRange(type.GetFields());
members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));
object obj = Activator.CreateInstance(type);
foreach (MemberInfo member in members)
{
JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));
if (jsonProperty != null && dictionary.ContainsKey(jsonProperty.Name))
{
SetMemberValue(serializer, member, obj, dictionary[jsonProperty.Name]);
}
else if (dictionary.ContainsKey(member.Name))
{
SetMemberValue(serializer, member, obj, dictionary[member.Name]);
}
else
{
KeyValuePair<string, object> kvp = dictionary.FirstOrDefault(x => string.Equals(x.Key, member.Name, StringComparison.InvariantCultureIgnoreCase));
if (!kvp.Equals(default(KeyValuePair<string, object>)))
{
SetMemberValue(serializer, member, obj, kvp.Value);
}
}
}
return obj;
}
private void SetMemberValue(JavaScriptSerializer serializer, MemberInfo member, object obj, object value)
{
if (member is PropertyInfo)
{
PropertyInfo property = (PropertyInfo)member;
property.SetValue(obj, serializer.ConvertToType(value, property.PropertyType), null);
}
else if (member is FieldInfo)
{
FieldInfo field = (FieldInfo)member;
field.SetValue(obj, serializer.ConvertToType(value, field.FieldType));
}
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Type type = obj.GetType();
List<MemberInfo> members = new List<MemberInfo>();
members.AddRange(type.GetFields());
members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));
Dictionary<string, object> values = new Dictionary<string, object>();
foreach (MemberInfo member in members)
{
JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));
if (jsonProperty != null)
{
values[jsonProperty.Name] = GetMemberValue(member, obj);
}
else
{
values[member.Name] = GetMemberValue(member, obj);
}
}
return values;
}
private object GetMemberValue(MemberInfo member, object obj)
{
if (member is PropertyInfo)
{
PropertyInfo property = (PropertyInfo)member;
return property.GetValue(obj, null);
}
else if (member is FieldInfo)
{
FieldInfo field = (FieldInfo)member;
return field.GetValue(obj);
}
return null;
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new[] { typeof(DataObject) };
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute
{
public JsonPropertyAttribute(string name)
{
Name = name;
}
public string Name
{
get;
set;
}
}
DataObject
클래스는 다음이된다 :
public class DataObject
{
[JsonProperty("user_id")]
public int UserId { get; set; }
[JsonProperty("detail_level")]
public DetailLevel DetailLevel { get; set; }
}
나는 이것이 조금 늦을 수도 있음을 인정하지만 다른 사람들이 그것을 사용하기 JavaScriptSerializer
보다는 DataContractJsonSerializer
그것을 사용하고 싶어한다고 생각 했습니다.
JavaScriptConverter에서 상속 된 클래스를 만듭니다. 그런 다음 세 가지를 구현해야합니다.
행동 양식-
- 직렬화
- 역 직렬화
특성-
- 지원되는 유형
직렬화 및 역 직렬화 프로세스를 더 많이 제어해야하는 경우 JavaScriptConverter 클래스를 사용할 수 있습니다.
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomConverter() });
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
다음과 같이 Newtonsoft.Json을 사용했습니다. 개체 만들기 :
public class WorklistSortColumn
{
[JsonProperty(PropertyName = "field")]
public string Field { get; set; }
[JsonProperty(PropertyName = "dir")]
public string Direction { get; set; }
[JsonIgnore]
public string SortOrder { get; set; }
}
이제 아래와 같이 Json 객체로 직렬화하려면 아래 메서드를 호출하십시오.
string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);
가고 싶지 않은 사람들을 위해 Newtonsoft Json.Net 또는 DataContractJsonSerializer
어떤 이유 (내가 어떤 :) 생각할 수 없다)을 위해, 여기의 구현입니다 JavaScriptConverter
그 지원 DataContract
과 enum
로 string
변환 -
public class DataContractJavaScriptConverter : JavaScriptConverter
{
private static readonly List<Type> _supportedTypes = new List<Type>();
static DataContractJavaScriptConverter()
{
foreach (Type type in Assembly.GetExecutingAssembly().DefinedTypes)
{
if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
{
_supportedTypes.Add(type);
}
}
}
private bool ConvertEnumToString = false;
public DataContractJavaScriptConverter() : this(false)
{
}
public DataContractJavaScriptConverter(bool convertEnumToString)
{
ConvertEnumToString = convertEnumToString;
}
public override IEnumerable<Type> SupportedTypes
{
get { return _supportedTypes; }
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
{
try
{
object instance = Activator.CreateInstance(type);
IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
.Concat(type.GetProperties().Where(property => property.CanWrite && property.GetIndexParameters().Length == 0))
.Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
foreach (MemberInfo member in members)
{
DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
object value;
if (dictionary.TryGetValue(attribute.Name, out value) == false)
{
if (attribute.IsRequired)
{
throw new SerializationException(String.Format("Required DataMember with name {0} not found", attribute.Name));
}
continue;
}
if (member.MemberType == MemberTypes.Field)
{
FieldInfo field = (FieldInfo)member;
object fieldValue;
if (ConvertEnumToString && field.FieldType.IsEnum)
{
fieldValue = Enum.Parse(field.FieldType, value.ToString());
}
else
{
fieldValue = serializer.ConvertToType(value, field.FieldType);
}
field.SetValue(instance, fieldValue);
}
else if (member.MemberType == MemberTypes.Property)
{
PropertyInfo property = (PropertyInfo)member;
object propertyValue;
if (ConvertEnumToString && property.PropertyType.IsEnum)
{
propertyValue = Enum.Parse(property.PropertyType, value.ToString());
}
else
{
propertyValue = serializer.ConvertToType(value, property.PropertyType);
}
property.SetValue(instance, propertyValue);
}
}
return instance;
}
catch (Exception)
{
return null;
}
}
return null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
if (obj != null && Attribute.IsDefined(obj.GetType(), typeof(DataContractAttribute)))
{
Type type = obj.GetType();
IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
.Concat(type.GetProperties().Where(property => property.CanRead && property.GetIndexParameters().Length == 0))
.Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
foreach (MemberInfo member in members)
{
DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
object value;
if (member.MemberType == MemberTypes.Field)
{
FieldInfo field = (FieldInfo)member;
if (ConvertEnumToString && field.FieldType.IsEnum)
{
value = field.GetValue(obj).ToString();
}
else
{
value = field.GetValue(obj);
}
}
else if (member.MemberType == MemberTypes.Property)
{
PropertyInfo property = (PropertyInfo)member;
if (ConvertEnumToString && property.PropertyType.IsEnum)
{
value = property.GetValue(obj).ToString();
}
else
{
value = property.GetValue(obj);
}
}
else
{
continue;
}
if (dictionary.ContainsKey(attribute.Name))
{
throw new SerializationException(String.Format("More than one DataMember found with name {0}", attribute.Name));
}
dictionary[attribute.Name] = value;
}
}
return dictionary;
}
}
참고 : 이것은 배치 된 어셈블리에 정의 된 클래스 DataContractJavaScriptConverter
만 처리 DataContract
합니다. 개별 어셈블리의 클래스를 원하는 경우 _supportedTypes
정적 생성자에서 그에 따라 목록을 수정합니다 .
다음과 같이 사용할 수 있습니다.
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new DataContractJavaScriptConverter(true) });
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
The DataObject
class would look like this -
using System.Runtime.Serialization;
[DataContract]
public class DataObject
{
[DataMember(Name = "user_id")]
public int UserId { get; set; }
[DataMember(Name = "detail_level")]
public string DetailLevel { get; set; }
}
Please note that this solution doesn't handle EmitDefaultValue
and Order
properties supported by DataMember
attribute.
My requirements included:
- must honor the dataContracts
- must deserialize dates in the format received in service
- must handle colelctions
- must target 3.5
- must NOT add an external dependency, especially not Newtonsoft (I'm creating a distributable package myself)
- must not be deserialized by hand
My solution in the end was to use SimpleJson(https://github.com/facebook-csharp-sdk/simple-json).
Although you can install it via a nuget package, I included just that single SimpleJson.cs file (with the MIT license) in my project and referenced it.
I hope this helps someone.
'Programing' 카테고리의 다른 글
Asp.Net MVC PDF를 생성하기 위해보기를 얻는 방법 (0) | 2020.10.27 |
---|---|
시스템에 설치된 애플리케이션 가져 오기 (0) | 2020.10.27 |
이전 git-commit을 복제하는 방법 (및 git에 대한 추가 질문) (0) | 2020.10.27 |
5 개의 오류 후에 GCC를 중지하도록 지시하는 방법은 무엇입니까? (0) | 2020.10.27 |
Python 정수 범위 (0) | 2020.10.27 |