I have a group of objects that i want to serialize.
In a specific class, i have two properties, and i know that always one of them it will be null. So, i want serialize this properties with same name and ignoring the one that is null.
The next code is an example. In this case Data is null and Data1 is not, but in the real case the conditions of the problem will determine which one will be null.
public class DataToSerialize
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TData Data { get; set; }
[JsonProperty("Data", NullValueHandling = NullValueHandling.Ignore)]
public TData1 Data1 { get; set; }
public DataToSerialize()
{
Data = null;
Data1 = new TData1();
}
}
When i try to serialize the object is thrown the next exception:
Newtonsoft.Json.JsonSerializationException: 'A member with the name 'Data' already exists on 'DataToSerialize'. Use the JsonPropertyAttribute to specify another name.'
The easiest option might be to decorate both with a [JsonIgnore] attribute, and then have a separate property which supplies the non-null value:
public class DataToSerialize
{
[JsonIgnore]
public TData Data { get; set; }
[JsonIgnore]
public TData1 Data1 { get; set; }
[JsonProperty("data")]
public object SerializableData
{
get { return Data1 == null ? (object)Data : Data1; }
}
}
Or if it's appropriate to your use case, simply use a generic class:
public class DataToSerialize<TData>
{
public TData Data { get; set; }
}
Related
I got this strange API response from one external service:
{emplooye: "Michael",age:"25",attachments:[{idAttachment: "23",attachmentPath:"C://Users/1"},{idAttachment: "24",attachmentPath:"C://Users/2"}]},{emplooye: "John",age:"30",attachments:{idAttachment: "25",attachmentPath:"C://Users/3"}}
Has anyone ever faced a situation where sometimes the "Attachment" property can be an array, sometimes it can be an object? I created a class to manipulate the data, but when I find an object, the code breaks.
I'm doing this in C#.
Class Used
public class Attachments
{
public string idAttachment{ get; set; }
public string attachmentPath{ get; set; }
}
public class Root
{
public string emplooye {get; set;}
public string age {get;set}
public List<Attachments> attachments { get; set; } = new List<Attachments>();
}
your json is not even close to json, should be something like this
var json = "[{\"emplooye\":\"Michael\",\"age\":\"25\",\"attachments\":[{\"idAttachment\":\"23\",\"attachmentPath\":\"C://Users/1\"},{\"idAttachment\":\"24\",\"attachmentPath\":\"C://Users/2\"}]},{\"emplooye\":\"John\",\"age\":\"30\",\"attachments\":{\"idAttachment\":\"25\",\"attachmentPath\":\"C://Users/3\"}}]";
Using Newtonsoft.Json you can create a JsonConstructor
using Newtonsoft.Json;
List<Data> data= JsonConvert.DeserializeObject<List<Data>>(json);
public class Data
{
public string emplooye { get; set; }
public string age { get; set; }
public List<Attachments> attachments { get; set; }
[JsonConstructor]
public Data(JToken attachments)
{
if (attachments.Type.ToString() == "Array")
this.attachments = attachments.ToObject<List<Attachments>>();
else
this.attachments = new List<Attachments> { attachments.ToObject<Attachments>() };
}
public Data() {}
}
public class Attachments
{
public string idAttachment { get; set; }
public string attachmentPath { get; set; }
}
You can use Newtonsoft to parse to a JToken which will handle the typing for you, but with the downside of not having a stable and predictable class to deserialize to automatically
Then, you would want to check its type, which returns a JTokenType enum
Once you know what the underlying types are, marshal the data into your DTO classes
JToken responseJT = JToken.Parse(json); //json string
if (responseJT.Type == JTokenType.Array)
//its an array, handle as needed ...
else if (responseJT.Type == JTokenType.Object)
//its an object, handle as needed ...
Personally, I would keep the attachments property as a List<Attachments> and if the JToken has a JSON object I would just set it as the [0] index of that property. This way things stay consistent and you can use LINQ on that property with ease
I'm pulling some data from an external API, and they have some objects defined by a discriminator string.
An example is an array of "Include" objects, where each one can have a different type of object as the Attributes parameter.
public class Include
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("attributes")]
public T Attributes { get; set; }
}
How can I define what object type T is, based on the Type parameter's value?
You could define Attributes as a JObject, then do .ToObject based on what the Type value is.
public class Include
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("attributes")]
public JObject Attributes { get; set; }
}
Then do an if (or switch) statement to handle each Type:
if (include.Type == "TypeOne")
ProcessTypeOne(include.Attributes.ToObject<TypeOne>());
else
...
That's assuming you have a class defined for each possible "Type". Or you can process them however you need to, just convert the Attributes to the necessary Type via the .ToObject<>() method.
You could do something like this:
using System;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
//setup
var a = new Include{Type = "Baz", Attributes = new Baz{Prop1 = "hello"}};
//convert
var b = Convert.ChangeType(a.Attributes, Type.GetType(a.Type));
//use
Console.WriteLine(b.Prop1);
}
}
public class Include
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("attributes")]
public dynamic Attributes { get; set; }
}
public class Baz
{
public string Prop1 { get; set; }
}
Convert.ChangeType would allow you to convert the dynamic object to whatever type it needed to be. However you may still find that you are in same place as #BraianM's answer and will have to type check so you know that a property or method is there.
You could also look at JsonConverter. Perhaps there is something there that would help.
I'm using Json.net api JsonConvert.PopulateObject which accepts two parameters first the json string and second the actual object which you want to fill.
The structure of the object that I want to fill is
internal class Customer
{
public Customer()
{
this.CustomerAddress = new Address();
}
public string Name { get; set; }
public Address CustomerAddress { get; set; }
}
public class Address
{
public string State { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
My json string is
{
"Name":"Jack",
"State":"ABC",
"City":"XX",
"ZipCode":"098"
}
Now the Name property gets filled becuase it is present in json string but the CustomerAddress is not getting populated. Is there any way by which I can tell the Json.net library that populate CustomerAddress.City from the City property in the json string ?
Directly - no.
But it should be possible to achieve that, e.g. here is an attempt (assuming you can't change json):
class Customer
{
public string Name { get; set; }
public Address CustomerAddress { get; set; } = new Address(); // initial value
// private property used to get value from json
// attribute is needed to use not-matching names (e.g. if Customer already have City)
[JsonProperty(nameof(Address.City))]
string _city
{
set { CustomerAddress.City = value; }
}
// ... same for other properties of Address
}
Other possibilities:
change json format to contain Address object;
custom serialization (e.g. using binder to serialize fake type and convert it to needed);
... (should be more).
I am writing a WCF service that generates various XML and JSON formats for multiple clients. The code below generates a SerializationException: 'TPH_PriceListJsonItems' is a collection type and cannot be serialized when assigned to an interface type that does not implement IEnumerable ('TPH_IPriceListItems'). The XML part is working fine, but not JSON. I do not understand the error, my interface is implementing IEnumerable to represent a class wrapping a simple List<> so I can use the CollectionDataContract.
public class ReproduceDataContractIssue
{
public static void Main(String[] args)
{
// Create test object - vacation products lowest prices grid
TPH_IPriceList priceList = new TPH_PriceListJson();
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Cancun", StayDuration = 7, LowestPrice = 1111 });
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Jamaica", StayDuration = 14, LowestPrice = 2222 });
// Serialize into XML string
DataContractSerializer serializer = new DataContractSerializer(priceList.GetType());
MemoryStream memStream = new MemoryStream();
serializer.WriteObject(memStream, priceList);
memStream.Seek(0, SeekOrigin.Begin);
string xmlOutput;
using (var streamReader = new StreamReader(memStream))
xmlOutput = streamReader.ReadToEnd();
// Serialize into JSON string
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(priceList.GetType());
jsonSerializer.WriteObject(memStream = new MemoryStream(), priceList);
memStream.Seek(0, SeekOrigin.Begin);
string jsonOutput;
using (var streamReader = new StreamReader(memStream))
jsonOutput = streamReader.ReadToEnd();
}
}
public interface TPH_IPriceList
{
TPH_IPriceListItems ListItems { get; set; }
}
public interface TPH_IPriceListItems : IEnumerable<TPH_IPriceListItem>, IEnumerable, IList<TPH_IPriceListItem>
{
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public TPH_IPriceListItems ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
public TPH_PriceListJsonItem()
{
}
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>, TPH_IPriceListItems, IEnumerable<TPH_IPriceListItem>, IEnumerable
{
public TPH_PriceListJsonItems(int capacity)
: base(capacity)
{
}
public TPH_PriceListJsonItems()
: base()
{
}
}
}
The inconsistency arises from a difference in how JSON and XML represent collections. For XML, the data contract serializers convert a collection to a nested set of elements -- an outer collection wrapper, and an inner element for each item in the collection. For JSON, the serializers convert a collection to an array containing objects. This seems reasonable, but there's a difference between the two: the XML outer element can have its own XML attributes, but JSON arrays cannot have their own properties -- there's simply no place for them in the standard.
This becomes an issue in dealing with type hints. Type hints are properties added to the serialized data to indicate, in the event of serializing an interface or base class of a class hierarchy, what concrete class was actually serialized. They are required to enable deserialization of the object without data loss. In XML, they appear as an i:type attribute:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V1">
<ListItems i:type="ListItems"> <!-- Notice the type hint here. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- Notice the type hint here also. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
As you can see from your own example, type hints can be added for both collection and non-collection classes.
In JSON objects, they appear as an added property named "__type":
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
}
But, as mentioned before, JSON arrays cannot have properties. So, what does DataContractJsonSerializer do for polymorphic collection types? Well, other than for a few standard collection interfaces which, as Fabian notes, are mapped to collection classes using hardcoded logic, it throws a cryptic exception to indicate that subsequent deserialization would be impossible. (For comparison, Json.NET introduces an extra container object to hold collection type information. See TypeNameHandling setting.)
The solution to this inconsistency is to serialize the collection explicitly as a concrete collection (TPH_PriceListJsonItems in your case) rather than as an interface:
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[IgnoreDataMember]
public TPH_IPriceListItems ListItems
{
get
{
return ListItemList;
}
set
{
var list = value as TPH_PriceListJsonItems;
if (list == null)
{
list = new TPH_PriceListJsonItems();
if (value != null)
list.AddRange(value);
}
ListItemList = list;
}
}
[DataMember(Name = "ListItems")]
TPH_PriceListJsonItems ListItemList { get; set; }
public TPH_PriceListJson()
{
ListItemList = new TPH_PriceListJsonItems();
}
}
This eliminates the need for the type hint on the collection element while retaining it for collection members. It generates the following XML:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V3">
<ListItems> <!-- No type hint here any more. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- But the type hint is still here. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
And produces the following JSON:
{
"ListItems": [
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
},
]
}
This design allows the class TPH_IPriceListItems to control exactly what type of collection is used internally rather than leaving it up to users of the class, and thus seems like a preferable design overall.
Looks like it is a similar problem to this one. The List of supported interfaces in DataContractJsonSerializer is hardcoded. So you can't add your own List Wrapper interface.
Why don't you just drop TPH_IPriceListItems like in the following code ? It is simpler and should also do what you want:
public interface TPH_IPriceList
{
IList<TPH_IPriceListItem> ListItems { get; set; }
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public IList<TPH_IPriceListItem> ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>
{
}
I have a Dictionary<string, object> as an insertion input for MongoDB.Save().
When I fill this dictionary with primitive types - the serialization process works fine but when I'm trying to insert my dictionary filled with some custom class (for example - Person class) I'm getting the next error:
.NET type 'Person' cannot be mapped to a BsonValue
I noticed that if I insert my custom type converted to BsonDocument the serialization process work great.
How can I define MongoDB to serialize a particular class as BsonDocument?
Update: real code provided
here is my SensorData class:
[DataContract]
public class SensorData
{
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Type { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Desc { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public SensorStatus Status { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Value { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string ValueUnits { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Boolean)]
public bool Event { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Int32)]
public int TTL { get; set; }
}
Here is the methods which saves the data to MongoDB:
public void Save(Dictionary<string, object> RawSensorMessageDictionary)
{
try
{
var policyColl = new MongoClient(ConnString).GetServer().GetDatabase(DB_NAME).GetCollection<BsonDocument>(CollectionNames.RawSensorMessage.ToString());
if (!RawSensorMessageDictionary.Any(p => p.Key == "_id")) //if rawSensorMessageID is empty, mongodb will set it
RawSensorMessageDictionary.Add("_id", ObjectId.GenerateNewId().ToString());
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
policyColl.Save(bsonObj);
}
catch (Exception ex)
{
//log exception
}
}
And here is the code from my UnitTest:
DataAccess.MongoDAL dal = new DataAccess.MongoDAL();
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("1", true);
dic.Add(Message.RESOURCE_TYPE, "aaa");
dic.Add(Message.RESOURCE_NAME, "bbb");
dic.Add(Message.SENSOR_DATA, new SensorData { Desc = "aaa", Event = true, Status = SensorStatus.Active, TTL = 4, Type = "sss", Value = "222" });
dal.SaveRawSensorData(dic );
I want my app to Serialize automatically SensorData object to BsonDocument. If I don't do it manually I'm getting the following exception: .NET type 'SensorData' cannot be mapped to a BsonValue
How should I do it?
Update 2:
OK found the issue, In my "save to db" method I used the next code in order to convert my dictionary BsonDocument:
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
I would expect that creating a new instance of 'BsonDocument' from other object will handle the conversion exactly like object.ToBsonDocument() does but apparently it's not.
Finally I replaced the code above to the following one:
var bsonObj = RawSensorMessageDictionary.ToBsonDocument();
dal.SaveRawSensorData(dic );
now it works.
If the string in the dictionary is the property in your mongodb document, than a non primitive type is a subdocument in mongodb. Please try to mark all properties in the class with [BsonElement] and the Bson Id with [BsonId]. And be sure you follow all rules from here: Serialize Documents with the C# Driver
It would be nice, if you provide some more information
EDIT 1
I would try to remove all properties of SensorData besides Value. If this works, i would than add all other (one by one).