How to avoid incompatible during deserializing polymorphic properties in MassTransit? - c#

When using the request/response pattern, during the deserialization of the response, an incompatibility error occurs. Apparently, the publisher serializer configuration is causing this issue, although the message is as expected.
Using the JsonProperty feature could isolate the problem, however, it does not reflect what was expected.
Versions
.NET: 6.0;
MassTRansit: 7.3.0;
Newtonsoft: 13.0.1
Type specified in JSON 'Messages.Models+CreditCard, Messages'
is not compatible with 'GreenPipes.DynamicInternal.Models\+Messages.Models\+IPaymentMethod'
Resource:
_bus.CreateRequestClient<TMessage>().GetResponse<TResponse>(message, cancellationToken);
Publisher serializer configuration:
bus.ConfigureJsonSerializer(settings =>
{
settings.TypeNameHandling = TypeNameHandling.Objects;
return settings;
});
Message response serialization:
{
"message": {
"$type": "Messages.Services.ShoppingCarts.Responses+CartDetails, Messages",
"cartItems": [
{
"$type": "Messages.Models+Item, Messages",
"productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"productName": "string",
"unitPrice": "10.0",
"quantity": 30,
"pictureUrl": "string"
}
],
"paymentMethods": [
{
"$type": "Messages.Models+CreditCard, Messages",
"id": "be7c40ac-1cd1-4e35-bccf-a1a2f4efecfd",
"expiration": "01/22",
"holderName": "string",
"number": "374245455400126",
"securityNumber": "string"
},
{
"$type": "Messages.Models+PayPal, Messages",
"id": "9465cf12-a322-477d-94c8-116d03a8399e",
"Password": "123",
"UserName": "string"
}
],
...
}
}
Consumer deserializer configuration:
bus.ConfigureJsonDeserializer(settings =>
{
settings.TypeNameHandling = TypeNameHandling.Objects; // or Auto, All, etc...
return settings;
});
Error:
System.Runtime.Serialization.SerializationException: A JSON serialization exception occurred while deserializing the message envelope
---> Newtonsoft.Json.JsonSerializationException: Type specified in JSON 'Messages.Models+CreditCard, Messages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
is not compatible with 'GreenPipes.DynamicInternal.Models\+Messages.Models\+IPaymentMethod, MessagesGreenPipes.DynamicInternale7ccc67139ad479db488c4fa6310335a, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
Path 'message.paymentMethods[0].$type', line 30, position 55.
JsonProperty does not work:
public record CartDetails : Response
{
[JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public IEnumerable<Models.IPaymentMethod> PaymentMethods { get; init; }
}
{
"message": {
"cartItems": [
{
"productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"productName": "string",
"unitPrice": "10.0",
"quantity": 30,
"pictureUrl": "string"
}
],
"paymentMethods": [
{
"id": "be7c40ac-1cd1-4e35-bccf-a1a2f4efecfd",
"expiration": "01/22",
"holderName": "string",
"number": "374245455400126",
"securityNumber": "string"
},
{
"id": "9465cf12-a322-477d-94c8-116d03a8399e",
"Password": "123",
"UserName": "string"
}
],
...
}
}
Response
public record CartDetails : Response
{
// [JsonProperty(TypeNameHandling = TypeNameHandling.Objects)]
public IEnumerable<Models.IPaymentMethod> PaymentMethods { get; init; }
public IEnumerable<Models.Item> CartItems { get; init; }
public Guid UserId { get; init; }
public decimal Total { get; init; }
public Guid Id { get; init; }
public bool IsDeleted { get; init; }
}
Types:
public interface IPaymentMethod
{
Guid Id { get; }
decimal Amount { get; }
}
public record CreditCard : IPaymentMethod
{
public Guid Id { get; init; }
public decimal Amount { get; init; }
[property: JsonConverter(typeof(ExpirationDateOnlyJsonConverter))]
public DateOnly Expiration { get; init; }
public string HolderName { get; init; }
public string Number { get; init; }
public string SecurityNumber { get; init; }
}
public record PayPal : IPaymentMethod
{
public Guid Id { get; init; }
public decimal Amount { get; init; }
public string UserName { get; init; }
public string Password { get; init; }
}

Thanks Wiktor Zychla, the issue led me to Nkot's workaround, which served me very well:
TypeNameHandlingConverter:
internal class TypeNameHandlingConverter : JsonConverter
{
private readonly TypeNameHandling _typeNameHandling;
public TypeNameHandlingConverter(TypeNameHandling typeNameHandling)
{
_typeNameHandling = typeNameHandling;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> new JsonSerializer { TypeNameHandling = _typeNameHandling }.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> new JsonSerializer { TypeNameHandling = _typeNameHandling }.Deserialize(reader, objectType);
public override bool CanConvert(Type objectType)
=> IsMassTransitOrSystemType(objectType) is false;
private static bool IsMassTransitOrSystemType(Type objectType)
=> objectType.Assembly == typeof(IConsumer).Assembly ||
objectType.Assembly.IsDynamic ||
objectType.Assembly == typeof(object).Assembly;
}
Consumer deserializer configuration:
bus.ConfigureJsonDeserializer(settings =>
{
settings.Converters.Add(new TypeNameHandlingConverter(TypeNameHandling.Objects));
return settings;
});

Related

How to deserialize Json into an C# object and fill the components into a dictionary

{
"success": true,
"data": [{
"type": "Employee",
"attributes": {
"id": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"email": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"dynamic_2682839": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
},
public class Id
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public int Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Email
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Dynamic2682839
{
[JsonPropertyName("label")]
public string Label { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("universal_id")]
public string UniversalId { get; set; }
}
public class Attributes
{
[JsonPropertyName("id")]
public Id Id { get; set; }
[JsonPropertyName("email")]
public Email Email { get; set; }
[JsonPropertyName("dynamic_2682839")]
public Dynamic2682839 Dynamic2682839 { get; set; }
}
public class Datum
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("attributes")]
public Attributes Attributes { get; set; }
}
public class Root
{
[JsonPropertyName("success")]
public bool Success { get; set; }
[JsonPropertyName("data")]
public List<Datum> Data { get; set; }
}
I know it is probably are very simple solution behind it but i cant get to it. My Problem is that I want to deserialize this type of data into an object for example. This json file goes on forever but the only thing I'm interested in are the "ID" and the "Personalnumber" so I can create a dictionary for further processing with my code.
I mean I could just sit there for two days and add everything to the dictionary but first of all it would drive me crazy and second of all the dictionary should add new members automatically to the dictionary and shouldn't be static. I already converted the Json into class objects and downloaded Newtonsoft for my IDE and with this comand:
Id ids = JsonConvert.DeserializeObject<Id>(json);
Console.WriteLine(ids.Value);
I try to get all the values from the ID´s but all i get is a 0.
The thing is i tested everything so far and it works perfectly but my deserialization dont work as planned.
If anyone could help a newbie I would appreciate it.
This looks wrong
Id ids = JsonConvert.DeserializeObject<Id>(json);
You should probably be deserializing to Root instead.
Root root = JsonConvert.DeserializeObject<Root>(json);
From there you can get all the IDs with
List<Id> ids = root.Data.Select(datum => datum.Attributes.Id);
This json file goes on forever but the only thing I'm interested in are the "ID" and the "Personalnumber" ... the dictionary should add new members automatically ... and shouldn't be static.
Phuzi's answer seizes onto the wrong thing, giving you the contents of the "id" object, which does get you the ID, but ignores the Personalnumber..
..which also looks like it might not always be in a dynamic_2682839
We need a different strategy for getting the data; we can look to see if the label contains ID or Personalnumber but we could really do with the serializes deser'ing this data to a common set of objects, not a different Type (with the same properties) each time
I don't know if you used QuickType.IO for generating your JSON classes but, if you didn't, I would recommend it; it's a bit more sophisticated than other generators. You can trick it into helping you more by modifying your JSON..
Take this:
{
"success": true,
"data": [
{
"type": "Employee",
"attributes": {
"id": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"email": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"dynamic_2682839": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
}
]
}
And make it into this (put some more objects in "attributes", and make the keys of the dictionary into sequential numbers):
{
"success": true,
"data": [{
"type": "Employee",
"attributes": {
"1": {
"label": "ID",
"value": 8556527,
"type": "integer",
"universal_id": "id"
},
"2": {
"label": "Email",
"value": "exapmle#gmail.com",
"type": "standard",
"universal_id": "email"
},
"3": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
},
"4": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
},
"5": {
"label": "DATEV Personalnumber",
"value": "31604",
"type": "standard",
"universal_id": "staff_number"
}
}
}]
}
which will help QT see more easily that a Dictionary<string, Attribute> is suitable for "attributes", and a custom converter is needed for the varying type of "value"...
Otherwise you'll just get multiple identical classes (your Id, Email and Dynamic_123 classes) and a property for every member of "attributes" even though they're all the same:
You thus get these classes out of QT:
namespace SomeNamespace
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class SomeRoot
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("data")]
public Datum[] Data { get; set; }
}
public partial class Datum
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("attributes")]
public Dictionary<string, Attribute> Attributes { get; set; }
}
public partial class Attribute
{
[JsonProperty("label")]
public string Label { get; set; }
[JsonProperty("value")]
public Value Value { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("universal_id")]
public string UniversalId { get; set; }
}
public partial struct Value
{
public long? Integer;
public string String;
public static implicit operator Value(long Integer) => new Value { Integer = Integer };
public static implicit operator Value(string String) => new Value { String = String };
}
public partial class SomeRoot
{
public static SomeRoot FromJson(string json) => JsonConvert.DeserializeObject<SomeRoot>(json, SomeNamespace.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this SomeRoot self) => JsonConvert.SerializeObject(self, SomeNamespace.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
ValueConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class ValueConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(Value) || t == typeof(Value?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Integer:
var integerValue = serializer.Deserialize<long>(reader);
return new Value { Integer = integerValue };
case JsonToken.String:
case JsonToken.Date:
var stringValue = serializer.Deserialize<string>(reader);
return new Value { String = stringValue };
}
throw new Exception("Cannot unmarshal type Value");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (Value)untypedValue;
if (value.Integer != null)
{
serializer.Serialize(writer, value.Integer.Value);
return;
}
if (value.String != null)
{
serializer.Serialize(writer, value.String);
return;
}
throw new Exception("Cannot marshal type Value");
}
public static readonly ValueConverter Singleton = new ValueConverter();
}
}
and you can get your ID/Personalnumber attributes like this:
var someRoot = SomeRoot.FromJson(File.ReadAllText("a.json"));
var attribs = someRoot.Data.SelectMany(d => d.Attributes.Where(a => a.Value.Label == "ID" || a.Value.Label.Contains("Personalnumber"))).ToArray();
or some other LINQ of your choosing on the Dictionary at the path "data"."attributes" - here I've chosen to SelectMany; if Data had 10 elements, and each element had 4 Attributes, 2 of which were ID or Personalnumber, you'd get a single array of 20 long, of the ID/Personalnumber Attributes. You might not want to SelectMany, if you want to keep them together (they're only loosely related by ordering in the array after a SelectMany)

Is using an "object" property a proper thing to do?

I have to get back an object the frontend is sending me. Thing is, front team wanted their component to be as generic as it can be so, in the dto, i'll recieve a Value property that can be different things (a boolean, a string, a list a strings, a numeric value...), as following :
"conditions": [
{
"alias": "FSTNM",
"providerKey": "Marketing",
"hasValue": true,
"conditionType": "Text",
"values": "john",
"startDate": null,
"endDate": null
},
{
"alias": "LSTNM",
"providerKey": "Marketing",
"hasValue": true,
"conditionType": "Text",
"values": null,
"startDate": null,
"endDate": null
},
{
"alias": "BTHDT",
"providerKey": "Marketing",
"hasValue": true,
"conditionType": "DateTime",
"values": null,
"startDate": "02-10-1980",
"endDate": "17-08-1989"
},
{
"alias": "AMECH",
"providerKey": "Custom",
"hasValue": true,
"conditionType": "Boolean",
"values": true,
"startDate": null,
"endDate": null
},
{
"alias": "CMBCH",
"providerKey": "Custom",
"hasValue": true,
"conditionType": "Number",
"values": 2,
"startDate": null,
"endDate": null
},
{
"alias": "FVRDR",
"providerKey": "Custom",
"hasValue": true,
"conditionType": "List",
"values": [
1,
3
],
"startDate": null,
"endDate": null
}
]
So, I tried to set the Values property as object like this :
public class DataTableFilterValueDTO
{
public string Alias { get; set; }
public string ProviderKey { get; set; }
public bool HasValue { get; set; }
public string ConditionType { get; set; }
public object Values { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
It seems that it works. When the request reack my controller, Values seems to be the right type, and depending on conditionType, I can cast it to the object i'm supposed to retrieve, like this :
If conditionType = "Text" :
var values = condition.Values as string;
If conditionType = "Boolean" :
var values = condition.Values as bool;
If conditionType = "List" :
var values = condition.Values as List<string>;
But is this a thing to do?
I mean, it does not feel right using this but I've never worked with the objecttype and can't really tell when it's good to use it.
You could implement a Custom JsonConverter. To do this, declare your class as abstract, and define a class that inherits it for each data type you expect to receive, like so:
[JsonConverter(typeof(ValueConverter))]
public abstract class Value
{
public string Alias { get; set; }
public string ProviderKey { get; set; }
public bool HasValue { get; set; }
public string ConditionType { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
public class BooleanValue : Value { public bool? Values { get; set; } }
public class ListValue : Value { public List<string> Values { get; set; } }
public class StringValue : Value { public string Values { get; set; } }
public class DateTimeValue : Value { public DateTime? Values { get; set; } }
public class IntegerValue : Value { public int? Values { get; set; } }
Then you need to define your custom JsonConverter where you perform the type conversion according to the value of the conditionType property:
public class ValueConverter : JsonConverter
{
static readonly JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new CustomResolver() };
public override bool CanConvert(Type objectType) => objectType == typeof(Value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
return (obj["conditionType"].Value<string>()) switch
{
"Text" => JsonConvert.DeserializeObject<StringValue>(obj.ToString(), SpecifiedSubclassConversion),
"DateTime" => JsonConvert.DeserializeObject<DateTimeValue>(obj.ToString(), SpecifiedSubclassConversion),
"Boolean" => JsonConvert.DeserializeObject<BooleanValue>(obj.ToString(), SpecifiedSubclassConversion),
"Number" => JsonConvert.DeserializeObject<IntegerValue>(obj.ToString(), SpecifiedSubclassConversion),
"List" => JsonConvert.DeserializeObject<ListValue>(obj.ToString(), SpecifiedSubclassConversion),
_ => throw new Exception("Unknown conditionType"),
};
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class CustomResolver: DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(Value).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null;
return base.ResolveContractConverter(objectType);
}
}
And finally the usage:
foreach (var value in JsonConvert.DeserializeObject<List<Value>>(json))
{
if (value is StringValue)
{
string s = ((StringValue)value).Values;
}
}

Convert nested Firebase database object to C# object using Json.NET

I need to convert firebase-ish data structure to C# object using JSON.Net library.
I store my data on Firebase Database like following;
{
"PoolDatas": {
"-LGGJGTAv_DPtzkmjIbl": {
"CoinType": 2,
"PoolType": 4,
"Alias": "First Alias",
"Address": "0xAE12EF212",
"Alarms": {
"-LGsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 2,
"Val": 100
},
"AlarmType": 3
},
"-LBAsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
}
},
"-LEAJGTAv_DPtzkmjIbl": {
"CoinType": 1,
"PoolType": 1,
"Alias": "Second Alias",
"Address": "0xAeeeEF212",
"Alarms": {
"-LGsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 10,
"Val": 120
},
"AlarmType": 1
},
"-LBAsdsdv_DPtzkmjIbl": {
"Threshold": {
"Type": 1,
"Val": 250
},
"AlarmType": 2
}
}
}
}
}
All list objects have firebase generated ID. I cannot map this data to C# class because firebase IDs don't comply with the list structure.
Please see my model in C# side;
public class PoolData
{
public string FirebaseId{ get; set; }
public string Alias { get; set; }
public PoolType PoolType { get; set; } //enum
public CoinType CoinType { get; set; } //enum
public string Address { get; set; }
public List<Alarm> Alarms { get; set; }
}
public class Alarm
{
public string FirebaseId{ get; set; }
public AlarmType AlarmType{ get; set; } //enum
public Threshold Threshold { get; set; } //object
}
public class Threshold
{
public ThresholdType Type{ get; set; } //enum
public int Value { get; set; }
}
In order to be able to convert Firebase-ish json into C# object I need a json like this;
{
"PoolDatas": [
{
"FirebaseId": "-LGGJGTAv_DPtzkmjIbl",
"CoinType": 1,
"PoolType": 1,
"Alias": "First Alias",
"Alarms": [
{
"FirebaseId": "-LGsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 1
},
{
"FirebaseId": "-LBAsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
],
"Address": "0xAE12EF212"
},
{
"FirebaseId": "-LEAJGTAv_DPtzkmjIbl",
"CoinType": 1,
"PoolType": 1,
"Alias": "First Alias",
"Alarms": [
{
"FirebaseId": "-LGsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 1
},
{
"FirebaseId": "-LBAsdsdv_DPtzkmjIbl",
"Threshold": {
"Type": 1,
"Val": 1
},
"AlarmType": 2
}
],
"Address": "0xAE12EF212"
}
]
}
How can I obtain this json from the first one?
I tried several recursive processes using JObject loops but it didn't work.
Thanks in advance!
You can deserialize the JSON to a Dictionary<string, PoolData>, change each value so the key of the dictionary goes into the FirebaseId property, and then put it into an array, something like this:
class Origin
{
public Dictionary<string, PoolData> PoolDatas { get; set; }
}
class Destination
{
public PoolData[] PoolDatas { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("data.json");
var data = JsonConvert.DeserializeObject<Origin>(json);
var destination = new Destination();
destination.PoolDatas = data.PoolDatas.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToArray();
}
}
You will need to install and import the Json.Net package using Newtonsoft.Json;.
EDIT: as mentioned in the comments, this will not work for nested objects. For that scenario, maybe you could use a custom serializer, something like this:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ConsoleApp1
{
public interface IFirebaseObject
{
string FirebaseId { get; set; }
}
public class PoolData
{
public string FirebaseId { get; set; }
public string Alias { get; set; }
public string Address { get; set; }
[JsonConverter(typeof(MapToArrayConverter<Alarm>))]
public List<Alarm> Alarms { get; set; }
}
public class Alarm: IFirebaseObject
{
public string FirebaseId { get; set; }
public Threshold Threshold { get; set; } //object
}
public class Threshold
{
public int Value { get; set; }
}
class Origin
{
public Dictionary<string, PoolData> PoolDatas { get; set; }
}
class Destination
{
public PoolData[] PoolDatas { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("data.json");
var data = JsonConvert.DeserializeObject<Origin>(json);
var destination = new Destination();
destination.PoolDatas = data.PoolDatas.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToArray();
}
}
public class MapToArrayConverter<T> : JsonConverter where T : IFirebaseObject
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
JObject item = JObject.Load(reader);
Dictionary<string, T> value = JsonConvert.DeserializeObject<Dictionary<string, T>>(item.ToString());
// TODO also consider single values instead of lists
return value.Select(i =>
{
i.Value.FirebaseId = i.Key;
return i.Value;
}).ToList();
} else
{
return null;
}
}
public override bool CanConvert(Type objectType)
{
// TODO validate the object type
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// TODO implement the reverse method to also write
throw new NotImplementedException();
}
}
}
But as you can see the solution starts to get somewhat complex. Unless you need to do this for many types, maybe manually converting would be less time consuming and simpler to maintain.

deserialize json with different propertyValue types

I want to deserialize (and afterwards serialize again) the following Json
{
"id": "12345",
"custom_fields": [
{
"definition": "field1",
"value": "stringvalue"
},
{
"definition": "field2",
"value": [ "arrayvalue1", "arrayvalue2" ]
},
{
"definition": "field3",
"value": {
"type": "user",
"id": "1245"
}
}
]
}
The type of the value if different (field1: string, field2: array, field3: an extra nested structure).
This what I have:
public class CustomField
{
public string Definition { get; set; }
public object Value { get; set; }
}
public class RootObject
{
public string Id { get; set; }
public List<CustomField> Custom_fields { get; set; }
}
The Value-field is defined as object, but this is a problem when serializing again, especially the nested structure for field3.
How can I solve this?
You could implement a custom converter for your Value property:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Test
{
public class Program
{
static void Main(string[] args)
{
var json =
#"{
""id"": ""12345"",
""custom_fields"": [
{
""definition"": ""field1"",
""value"": ""stringvalue""
}, {
""definition"": ""field2"",
""value"": [ ""arrayvalue1"", ""arrayvalue2"" ]
}, {
""definition"": ""field3"",
""value"": {
""type"": ""user"",
""id"": ""1245""
}
}
]
}";
var rootObject = JsonConvert.DeserializeObject<RootObject>(json);
Console.WriteLine(JsonConvert.SerializeObject(rootObject));
}
}
public class RootObject
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "custom_fields")]
public List<CustomField> CustomFields { get; set; }
}
public class CustomField
{
[JsonProperty(PropertyName = "definition")]
public string Definition { get; set; }
[JsonProperty(PropertyName = "value")]
[JsonConverter(typeof(CustomValueConverter))]
public dynamic Value { get; set; }
}
public class CustomValueConverter : JsonConverter
{
// By returning false, we let the default `WriteJson` kick in
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return string.Empty;
}
if (reader.TokenType == JsonToken.String)
{
return serializer.Deserialize<string>(reader);
}
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<string[]>(reader);
}
return serializer.Deserialize<Dictionary<string, string>>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

Datacontract object and string

I'm trying to parse some results from a web service in C# visual studio 2010 that are coming back in JSON but the field named data is sometime a string and sometimes an object. I'm not sure how to get the data contracts to handle that. When it runs through the deserialization it just returns a null result for the everything. If i try a web request that doesn't have any profile_results then works OK.
The JSON looks like this
{
"ListSingleOrganization": {
"id": 86270,
"short_name": "someshort name",
"long_name": "Long name",
"created_at": "2014-05-02T14:21:06Z",
"is_disabled": false,
"description": "Verbose description",
"renewed_at": "2014-05-02T14:21:07Z",
"alternate_id": null,
"website_url": "http://www.url.com",
"member_count": 50,
"pic_url": "https://www.somepicture.com/1.jpg",
"umbrella_id": 36016,
"keywords": "Fraternity, Leadership, Human Service, Scholarship",
"category": {
"id": 53282,
"name": "Fraternities"
},
"profile_responses": [
{
"element": {
"id": 51350,
"name": "Group Email Address",
"type": "Email"
},
"data": "groupsemail#gmail.com"
},
{
"element": {
"id": 1239634,
"name": "Please list your organization's Twitter handle so we may follow you with our office's account. (#something)",
"type": "TextField"
},
"data": "#handle"
},
{
"element": {
"id": 1192652,
"name": "Is this a new organization?",
"type": "Radio"
},
"data": {
"id": 2003570,
"name": "No"
}
}
]
}
}
the difference is based on the element type but i don't know how to account for that with them having the same name.
right now i have
[DataContract]
class ListSingleOrganization
{
//other members
[DataMember(Name = "profile_responses")]
public List<profile_responses> profile_responses { get; set; }
}
[DataContract]
class profile_responses
{
[DataMember(Name = "element")]
public element element { get; set; }
[DataMember(Name="data")]
public data data {get; set; }
}
[DataContract]
class element
{
[DataMember(Name = "id")]
public int id { get; set; }
[DataMember(Name = "name")]
public string name { get; set; }
[DataMember(Name = "type")]
public string type { get; set; }
}
[DataContract]
class data
{
[DataMember(Order=1)]
public string raw { get; set; }
[DataMember(Name = "file_name")]
public string file_name { get; set; }
[DataMember(Name = "url")]
public string url { get; set; }
[DataMember(Name = "id")]
public int id { get; set; }
[DataMember(Name = "name")]
public string name { get; set; }
}
You can create a custom formatter to read the property, check if it a string. If it's a string then create a new instance of your data class and set one of the properties to the string value. Otherwise, deserialize it to a your data object.
public class JsonDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(data));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
data returnVal = null;
// You may need to debug this and see which token type is being returned
// per data element and adjust, but the principle stands.
if (reader.TokenType == JsonToken.String)
{
returnVal = new data();
returnVal.raw = reader.ReadAsString();
}
else
{
returnVal = serializer.Deserialize<data>(reader);
}
return returnVal;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Assuming you only need to deserialize
throw new NotImplementedException();
}
}
Associate the JsonConverter with your property:
[DataMember(Name = "data")]
[JsonConverter(typeof(JsonDataConverter))]
public data data { get; set; }
EDIT: (answering follow up question)
Set this up in an isolated unit test and get it to work there first before trying to put this in a SSIS package. If you are using the DataContractSerializer then the JsonDataConverter class will not be invoked. You want to deserialize using Json.NET:
using Newtonsoft.Json;
. . .
var orgList JsonConvert.DeserializeObject<ListSingleOrganization>(webServiceResultString);
Couldn't get the DataContract Serializes to work with these irregularities so I went the dynamic object route and that worked.
Deserialize JSON into C# dynamic object?

Categories