如何根据属性反序列化Redis订阅返回的Success/Error两种对象?
Great question! Let's walk through your options here because deserializing polymorphic JSON responses (like your Success and Error objects) without an explicit type field is a super common scenario.
Can you use Either<Success, Error> directly?
Short answer: Not out of the box with Json.NET (Newtonsoft.Json). The library doesn’t have a built-in Either type, so JsonConvert.DeserializeObject<Either<Success, Error>>(msg) will throw an error unless you build custom support for it. That said, you can absolutely implement an Either type and a corresponding JSON converter to make this work—we’ll cover that later.
Practical Alternatives (No Custom Either Required)
1. Parse to JObject first, then check properties
This is the simplest, most straightforward approach if you don’t want to add custom types or libraries. You’ll parse the raw JSON into a JObject, check for unique properties that distinguish Success from Error, then convert to the appropriate type:
subscription.OnMessage = (channel, msg) => { var jsonObject = JObject.Parse(msg); // Check for a property unique to Success if (jsonObject.ContainsKey("Token")) { var success = jsonObject.ToObject<Success>(); // Handle your Success logic here Console.WriteLine($"Received success token: {success.Token}"); } // Check for a property unique to Error else if (jsonObject.ContainsKey("ErrorMessage")) { var error = jsonObject.ToObject<Error>(); // Handle your Error logic here Console.WriteLine($"Received error from {error.Name}: {error.ErrorMessage}"); } else { // Handle unexpected message format Console.WriteLine("Received unknown message type"); } };
2. Use a base class with a custom JSON converter
If you want a more type-safe approach, create a shared base class for your messages and write a converter that determines which subclass to deserialize into. You can also use a library like JsonSubtypes (via NuGet) to simplify this, but here’s how to do it manually:
First, define your base class and subclasses:
public abstract class RedisMessage { } public class Success : RedisMessage { public string Token { get; set; } public object Message { get; set; } } public class Error : RedisMessage { public string ErrorMessage { get; set; } public string Name { get; set; } }
Then create a custom converter:
public class RedisMessageConverter : JsonConverter<RedisMessage> { public override RedisMessage ReadJson(JsonReader reader, Type objectType, RedisMessage existingValue, bool hasExistingValue, JsonSerializer serializer) { var jObj = JObject.Load(reader); // Use property presence to decide type if (jObj.ContainsKey("Token")) return jObj.ToObject<Success>(serializer); if (jObj.ContainsKey("ErrorMessage")) return jObj.ToObject<Error>(serializer); throw new JsonSerializationException("Unknown message type"); } public override void WriteJson(JsonWriter writer, RedisMessage value, JsonSerializer serializer) { serializer.Serialize(writer, value); } }
Now use it in your subscription code:
var serializerSettings = new JsonSerializerSettings(); serializerSettings.Converters.Add(new RedisMessageConverter()); subscription.OnMessage = (channel, msg) => { var message = JsonConvert.DeserializeObject<RedisMessage>(msg, serializerSettings); switch (message) { case Success success: // Handle Success break; case Error error: // Handle Error break; default: // Handle unknown type break; } };
Implementing the Either Approach
If you really want to use an Either type for this scenario, you’ll need to define the type and a converter to handle it. Here’s a minimal implementation:
Step 1: Define the Either type
public class Either<TLeft, TRight> { public bool IsLeft { get; } public TLeft Left { get; } public TRight Right { get; } private Either(TLeft left) { IsLeft = true; Left = left; Right = default; } private Either(TRight right) { IsLeft = false; Left = default; Right = right; } // Implicit conversions for easier usage public static implicit operator Either<TLeft, TRight>(TLeft left) => new(left); public static implicit operator Either<TLeft, TRight>(TRight right) => new(right); }
Step 2: Create the JSON converter for Either
public class EitherConverter<TLeft, TRight> : JsonConverter<Either<TLeft, TRight>> { public override Either<TLeft, TRight> ReadJson(JsonReader reader, Type objectType, Either<TLeft, TRight> existingValue, bool hasExistingValue, JsonSerializer serializer) { var jObj = JObject.Load(reader); try { // Try deserializing to TLeft first (Success) var leftValue = jObj.ToObject<TLeft>(serializer); return leftValue; } catch (JsonSerializationException) { // Fall back to TRight (Error) if deserialization fails var rightValue = jObj.ToObject<TRight>(serializer); return rightValue; } } public override void WriteJson(JsonWriter writer, Either<TLeft, TRight> value, JsonSerializer serializer) { if (value.IsLeft) serializer.Serialize(writer, value.Left); else serializer.Serialize(writer, value.Right); } }
Step 3: Use it in your code
var settings = new JsonSerializerSettings(); settings.Converters.Add(new EitherConverter<Success, Error>()); subscription.OnMessage = (channel, msg) => { var result = JsonConvert.DeserializeObject<Either<Success, Error>>(msg, settings); if (result.IsLeft) { var success = result.Left; // Handle Success logic } else { var error = result.Right; // Handle Error logic } };
Final Recommendation
If you want simplicity and don’t mind a bit of manual property checking, the JObject approach is your best bet. If you prefer a more type-safe, scalable solution, go with the base class + converter method. The Either approach works but adds extra complexity unless you’re already using an Either type elsewhere in your codebase.
内容的提问来源于stack exchange,提问作者Gabriel Guedes

