Reputation: 34800
I have the following class:
public class MyRequest
{
public string Type {get;set;}
public string Source {get;set;}
}
I would like to serialize/deserialize the value for Source
from the JSON field named the value of Type
, for example:
{
"type": "bank",
"bank": "Some value"
}
or
{
"type": "card",
"card": "Some value"
}
Where both bind to the Source
property.
Upvotes: 3
Views: 13291
Reputation: 409
I recently encountered this kind of problem where I needed to consume an API which has a dynamic data contract, so I developed a package called SerializationInterceptor. Here's the GitHub link: https://github.com/essencebit/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
Example from below uses Newtonsoft.Json for serialization/deserialization. Of course you can use any other tool, as this package does not depend on any.
What you could do is to create an interceptor:
public class JsonPropertyInterceptorAttribute : SerializationInterceptor.Attributes.InterceptorAttribute
{
public JsonPropertyInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyAttribute))
{
}
protected override SerializationInterceptor.Attributes.AttributeBuilderParams Intercept(SerializationInterceptor.Attributes.AttributeBuilderParams originalAttributeBuilderParams)
{
object value;
switch (InterceptorId)
{
case "some id":
// For DESERIALIZATION you first need to deserialize the object here having the prop Source unmapped(we'll invoke the proper deserialization later to have Source prop mapped to the correct Json key),
// then get the value of the prop Type and assign it to variable from below.
// For SERIALIZATION you need somehow to have here access to the object you want to serialize and get
// the value of the Type prop and assign it to variable from below.
value = "the value of Type prop";
break;
default:
return originalAttributeBuilderParams;
}
originalAttributeBuilderParams.ConstructorArgs = new[] { value };
return originalAttributeBuilderParams;
}
}
Then place the interceptor on the Source prop:
public class MyRequest
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonPropertyInterceptor("some id")]
[JsonProperty("source")]
public string Source { get; set; }
}
Then you invoke the proper serialization/deserialization like this:
var serializedObj = SerializationInterceptor.Interceptor.InterceptSerialization(obj, objType, (o, t) =>
{
var serializer = new JsonSerializer { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
using var stream = new MemoryStream();
using var streamWriter = new StreamWriter(stream);
using var jsonTextWriter = new JsonTextWriter(streamWriter);
serializer.Serialize(jsonTextWriter, o, t);
jsonTextWriter.Flush();
return Encoding.Default.GetString(stream.ToArray());
})
var deserializedObj = SerializationInterceptor.Interceptor.InterceptDeserialization(@string, objType, (s, t) =>
{
var serializer = new JsonSerializer();
using var streamReader = new StreamReader(s);
using var jsonTextReader = new JsonTextReader(streamReader);
return serializer.Deserialize(jsonTextReader, t);
});
Upvotes: 1
Reputation: 63
My solution is: First create APIResultModel class:
public class APIResultModel<T> where T: APIModel, new()
{
public string ImmutableProperty { get; set; }
public T Result { get; set; }
public APIResultModel<T> Deserialize(string json)
{
var jObj = JObject.Parse(json);
T t = new T();
var result = jObj[t.TypeName()];
jObj.Remove(t.TypeName());
jObj["Result"] = result;
return jObj.ToObject<APIResultModel<T>>();
}
}
Second create APIModel abstract Class:
public abstract class APIModel
{
public abstract string TypeName();
}
Third create dynamic content Model class:
public class MyContentModel: APIModel
{
public string Property {get; set;}
public override string TypeName()
{
return "JsonKey";
}
}
When you need to deserialize a json string:
var jsonModel = new APIResultModel<MyContentModel>();
jsonModel = jsonModel.Deserialize(json);
MyContentModel dynimacModel = jsonModel.Result;
The Deserialize function is come from @Eser
Upvotes: 0
Reputation: 129697
You could create a custom JsonConverter
to handle the dynamic property name:
public class MyRequestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyRequest);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["type"];
MyRequest req = new MyRequest
{
Type = type,
Source = (string)jo[type ?? ""]
};
return req;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
MyRequest req = (MyRequest)value;
JObject jo = new JObject(
new JProperty("type", req.Type),
new JProperty(req.Type, req.Source));
jo.WriteTo(writer);
}
}
To use the converter, add a [JsonConverter]
attribute to your class like this:
[JsonConverter(typeof(MyRequestConverter))]
public class MyRequest
{
public string Type { get; set; }
public string Source { get; set; }
}
Here is a working round-trip demo: https://dotnetfiddle.net/o7NDTV
Upvotes: 4
Reputation: 12546
I would write custom serialization/deserialization methods
var req1 = new MyRequest() { Type = "card", Source = "SomeValue" };
var json = Serialize(req1);
var req2 = Deserialize<MyRequest>(json);
string Serialize<T>(T obj)
{
var jObj = JObject.FromObject(obj);
var src = jObj["Source"];
jObj.Remove("Source");
jObj[(string)jObj["Type"]] = src;
return jObj.ToString(Newtonsoft.Json.Formatting.Indented);
}
T Deserialize<T>(string json)
{
var jObj = JObject.Parse(json);
var src = jObj[(string)jObj["Type"]];
jObj.Remove((string)jObj["Type"]);
jObj["Source"] = src;
return jObj.ToObject<T>();
}
Upvotes: 1