Newtonsoft.JsonのDeserializeObject<T>(strJSONData)
を使用してWeb要求からデータを取得し、それをクラスオブジェクトに変換するコード(変更できません)があります(クラスを変更できます)。クラスプロパティを[DataMember(Name = "raw_property_name")]
で装飾することにより、生のJSONデータをクラスの正しいプロパティにマッピングできます。 JSON複雑なオブジェクトの子プロパティを単純なプロパティにマップする方法はありますか?以下に例を示します。
{
"picture":
{
"id": 123456,
"data":
{
"type": "jpg",
"url": "http://www.someplace.com/mypicture.jpg"
}
}
}
URL以外の画像オブジェクトの残りは気にしないので、C#クラスに複雑なオブジェクトを設定したくありません。私は本当に次のようなものが欲しいだけです:
[DataMember(Name = "picture.data.url")]
public string ProfilePicture { get; set; }
これは可能ですか?
単一の追加プロパティが必要な場合は、JSONをJObject
に解析し、ToObject()
を使用してJObject
からクラスを作成します。次に、SelectToken()
を使用して追加のプロパティを取得します。
したがって、クラスが次のようになっていると仮定します。
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public string Age { get; set; }
public string ProfilePicture { get; set; }
}
これを行うことができます:
string json = @"
{
""name"" : ""Joe Shmoe"",
""age"" : 26,
""picture"":
{
""id"": 123456,
""data"":
{
""type"": ""jpg"",
""url"": ""http://www.someplace.com/mypicture.jpg""
}
}
}";
JObject jo = JObject.Parse(json);
Person p = jo.ToObject<Person>();
p.ProfilePicture = (string)jo.SelectToken("picture.data.url");
フィドル: https://dotnetfiddle.net/7gnJCK
より洗練されたソリューションを好む場合は、カスタムJsonConverter
を作成して、JsonProperty
属性を説明どおりに動作させることができます。コンバーターはクラスレベルで動作し、上記の手法と組み合わせていくつかのリフレクションを使用して、すべてのプロパティを設定する必要があります。コードでは次のようになります。
class JsonPathConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
デモンストレーションのために、JSONが次のようになったと仮定します。
{
"name": "Joe Shmoe",
"age": 26,
"picture": {
"id": 123456,
"data": {
"type": "jpg",
"url": "http://www.someplace.com/mypicture.jpg"
}
},
"favorites": {
"movie": {
"title": "The Godfather",
"starring": "Marlon Brando",
"year": 1972
},
"color": "purple"
}
}
...そして、以前の情報に加えて、その人の好きな映画(タイトルと年)と好きな色に興味があります。最初にターゲットクラスを[JsonConverter]
属性でマークしてカスタムコンバーターに関連付け、次に各プロパティで[JsonProperty]
属性を使用して、名前として目的のプロパティパス(大文字と小文字を区別する)を指定します。ターゲットプロパティもプリミティブである必要はありません。ここでMovie
で行ったように、子クラスを使用できます(介在するFavorites
クラスが必要ないことに注意してください)。
[JsonConverter(typeof(JsonPathConverter))]
class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("picture.data.url")]
public string ProfilePicture { get; set; }
[JsonProperty("favorites.movie")]
public Movie FavoriteMovie { get; set; }
[JsonProperty("favorites.color")]
public string FavoriteColor { get; set; }
}
// Don't need to mark up these properties because they are covered by the
// property paths in the Person class
class Movie
{
public string Title { get; set; }
public int Year { get; set; }
}
すべての属性を設定したら、通常どおりデシリアライズすることができ、「機能する」はずです。
Person p = JsonConvert.DeserializeObject<Person>(json);
マークされた回答は、CamelCasePropertyNamesContractResolverなどの登録される可能性のあるIContractResolverを無視するため、100%完全ではありません。
また、変換可能に対してfalseを返すと、他のユーザーケースが防止されるため、return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
更新されたバージョンは次のとおりです。 https://dotnetfiddle.net/F8C8U8
リンクに示されているように、プロパティにJsonProperty
を設定する必要もなくなりました。
何らかの理由で上記のリンクが停止または爆発した場合、以下のコードも含まれます:
public class JsonPathConverter : JsonConverter
{
/// <inheritdoc />
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$"))
{
throw new InvalidOperationException($"JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots but name was ${jsonPath}."); // Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject();
foreach (PropertyInfo prop in properties)
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = att != null ? att.PropertyName : prop.Name;
if (serializer.ContractResolver is DefaultContractResolver)
{
var resolver = (DefaultContractResolver)serializer.ContractResolver;
jsonPath = resolver.GetResolvedPropertyName(jsonPath);
}
var nesting = jsonPath.Split('.');
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++)
{
if (i == nesting.Length - 1)
{
lastLevel[nesting[i]] = new JValue(prop.GetValue(value));
}
else
{
if (lastLevel[nesting[i]] == null)
{
lastLevel[nesting[i]] = new JObject();
}
lastLevel = (JObject)lastLevel[nesting[i]];
}
}
}
serializer.Serialize(writer, main);
}
}
する代わりに
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
あなたはしなければならない
lastLevel[nesting[i]] = JValue.FromObject(jValue);
それ以外の場合は
タイプのJSONオブジェクトタイプを特定できませんでした...
例外
完全なコードは次のようになります。
object jValue = prop.GetValue(value);
if (prop.PropertyType.IsArray)
{
if(jValue != null)
//https://stackoverflow.com/a/20769644/249895
lastLevel[nesting[i]] = JArray.FromObject(jValue);
}
else
{
if (prop.PropertyType.IsClass && prop.PropertyType != typeof(System.String))
{
if (jValue != null)
lastLevel[nesting[i]] = JValue.FromObject(jValue);
}
else
{
lastLevel[nesting[i]] = new JValue(jValue);
}
}
誰かが@BrianRogersのJsonPathConverterをWriteJson
オプションとともに使用する必要がある場合、解決策があります(ドットのみのパスでのみ機能します)。
CanWrite
プロパティを削除して、デフォルトで再びtrue
になるようにします。
WriteJson
コードを次のように置き換えます。
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
var properties = value.GetType().GetRuntimeProperties ().Where(p => p.CanRead && p.CanWrite);
JObject main = new JObject ();
foreach (PropertyInfo prop in properties) {
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
var nesting=jsonPath.Split(new[] { '.' });
JObject lastLevel = main;
for (int i = 0; i < nesting.Length; i++) {
if (i == nesting.Length - 1) {
lastLevel [nesting [i]] = new JValue(prop.GetValue (value));
} else {
if (lastLevel [nesting [i]] == null) {
lastLevel [nesting [i]] = new JObject ();
}
lastLevel = (JObject)lastLevel [nesting [i]];
}
}
}
serializer.Serialize (writer, main);
}
上で言ったように、これはdotsを含むパスに対してのみ機能します。その場合、他のケースを防ぐために、次のコードをReadJson
に追加する必要があります。
[...]
string jsonPath = (att != null ? att.PropertyName : prop.Name);
if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) {
throw new InvalidOperationException("JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots."); //Array operations not permitted
}
JToken token = jo.SelectToken(jsonPath);
[...]