I've serialized an object using Newtonsoft's JSON serializer, and the DateTime has come through as:
/Date(1237588418563+0000)/
When I $.evalJSON() on that, it is an object but I can't find any normal Date methods like toUTCString on it.
Any ideas what I can do with this?
Use one of the JsonConverters that come with Json.NET for working with dates to get a better format. JavaScriptDateTimeConverter will automatically give you a JavaScript date.
public class LogEntry
{
public string Details { get; set; }
public DateTime LogDate { get; set; }
}
[Test]
public void WriteJsonDates()
{
LogEntry entry = new LogEntry
{
LogDate = new DateTime(2009, 2, 15, 0, 0, 0, DateTimeKind.Utc),
Details = "Application started."
};
string defaultJson = JsonConvert.SerializeObject(entry);
// {"Details":"Application started.","LogDate":"\/Date(1234656000000)\/"}
string javascriptJson = JsonConvert.SerializeObject(entry, new JavaScriptDateTimeConverter());
// {"Details":"Application started.","LogDate":new Date(1234656000000)}
string isoJson = JsonConvert.SerializeObject(entry, new IsoDateTimeConverter());
// {"Details":"Application started.","LogDate":"2009-02-15T00:00:00Z"}
}
Documentation: Serializing Dates in JSON with Json.NET
I came up with a different approach which might be useful to some. Basically I create my own CustomDateConverter that I call when I need it. The converter takes 2 parameters, a date format e.g. yyyy-MM-dd HH:mm:ss and a TimeZoneInfo, which allows me to convert the date from UTC to the user's time zone:
public class JSONCustomDateConverter : DateTimeConverterBase
{
private TimeZoneInfo _timeZoneInfo;
private string _dateFormat;
public JSONCustomDateConverter(string dateFormat, TimeZoneInfo timeZoneInfo)
{
_dateFormat = dateFormat;
_timeZoneInfo = timeZoneInfo;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(TimeZoneInfo.ConvertTimeFromUtc(Convert.ToDateTime(value), _timeZoneInfo).ToString(_dateFormat));
writer.Flush();
}
You can use it like this:
var jsonString = JsonConvert.SerializeObject(myObject, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = new List<JsonConverter>() { new JSONCustomDateConverter("yyyy-MM-dd HH:mm:ss", loggedUser.Timezone) } });
Obviously you could remove anything related to time zone if you only want custom date formatting. Let me know it that helped!
As of Newtonsoft Json.Net version 4.5r5 you use the JsonPropertyAttribute Class class and set its ItemConverterType Property property.
Usage:
// class to be serialized
public class MyClass
{
[JsonProperty(ItemConverterType = typeof(JavaScriptDateTimeConverter))]
public DateTime? DateTime1;
public DateTime? DateTime2;
}
As I have observed this will set the DateTimeConverter for all properties in this class not just the one before which is declared.
Ran into the same problem, and found a solution based on the link from Adam:
new Date(yourDate.substr(yourDate.indexOf("(") + 1, 13) - 0));
It looks like a Unix timestamp, which javascript is easily able to convert into a date object. The - 0 is simply to make javascript treat the substr output as an integer... I guess you could Number() it as well, if you don't like the looks of - 0
The JSON object contained something like this:
var data = {"CreatedDate":"/Date(1327572000000-1000)/"});
///
var oddDateTimeZone = data.CreatedDate;
var utcDateTime = oddDateTimeZone.substr(oddDateTimeZone.indexOf("(")+1, 13);
var utcZone = oddDateTimeZone.substr(oddDateTimeZone.indexOf("-")+1, 4);
var utcDateTimeZone = new Date(Number(utcDateTime)-(Number(utcZone)));
but, still it would be better to fix the JSON object so the date function fired without using something like eval() or window[]. Maybe in jQuery. Not sure.
Don't forget that the offset could be + and not just - for the offset!
Sorry I simplify a bit #James Newton-King
string date = Newtonsoft.Json.JsonConvert.SerializeObject(DateTime.Now);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
This works for me
Related
Given a Base64 string, the following sample class will deserialize properly using Newtonsoft.Json, but not with System.Text.Json:
using System;
using System.Text.Json.Serialization;
public class AvatarImage{
public Byte[] Data { get; set; } = null;
public AvatarImage() {
}
[JsonConstructor]
public AvatarImage(String Data) {
//Remove Base64 header info, leaving only the data block and convert it to a Byte array
this.Data = Convert.FromBase64String(Data.Remove(0, Data.IndexOf(',') + 1));
}
}
With System.Text.Json, the following exception is thrown:
must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.
Apparently System.Text.Json doesn't like the fact the property is a Byte[] but the parameter is a String, which shouldn't really matter because the whole point is that the constructor should be taking care of the assignments.
Is there any way to get this working with System.Text.Json?
In my particular case Base64 images are being sent to a WebAPI controller, but the final object only needs the Byte[]. In Newtonsoft this was a quick and clean solution.
This is apparently a known restriction of System.Text.Json. See the issues:
[JsonSerializer] Relax restrictions on ctor param type to immutable property type matching where reasonable #44428 which is currently labeled with the 6.0.0 Future milestone.
System.Text.Json incorrectly requires construct parameter types to match immutable property types. #47422
JsonConstrutor different behavior between Newtonsoft.Json and System.Text.Json #46480.
Thus (in .Net 5 at least) you will need to refactor your class to avoid the limitation.
One solution would be to add a surrogate Base64 encoded string property:
public class AvatarImage
{
[JsonIgnore]
public Byte[] Data { get; set; } = null;
[JsonInclude]
[JsonPropertyName("Data")]
public string Base64Data
{
private get => Data == null ? null : Convert.ToBase64String(Data);
set
{
var index = value.IndexOf(',');
this.Data = Convert.FromBase64String(index < 0 ? value : value.Remove(0, index + 1));
}
}
}
Note that, ordinarily, JsonSerializer will only serialize public properties. However, if you mark a property with [JsonInclude] then either the setter or the getter -- but not both -- can be nonpublic. (I have no idea why Microsoft doesn't allow both to be private, the data contract serializers certainly support private members marked with [DataMember].) In this case I chose to make the getter private to reduce the chance the surrogate property is serialized by some other serializer or displayed via some property browser.
Demo fiddle #1 here.
Alternatively, you could introduce a custom JsonConverter<T> for AvatarImage
[JsonConverter(typeof(AvatarConverter))]
public class AvatarImage
{
public Byte[] Data { get; set; } = null;
}
class AvatarConverter : JsonConverter<AvatarImage>
{
class AvatarDTO { public string Data { get; set; } }
public override AvatarImage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dto = JsonSerializer.Deserialize<AvatarDTO>(ref reader, options);
var index = dto.Data?.IndexOf(',') ?? -1;
return new AvatarImage { Data = dto.Data == null ? null : Convert.FromBase64String(index < 0 ? dto.Data : dto.Data.Remove(0, index + 1)) };
}
public override void Write(Utf8JsonWriter writer, AvatarImage value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, new { Data = value.Data }, options);
}
This seems to be the easier solution if for simple models, but can become a nuisance for complex models or models to which properties are frequently added.
Demo fiddle #2 here.
Finally, it seems a bit unfortunate that the Data property will have some extra header prepended during deserialization that is not present during serialization. Rather than fixing this during deserialization, consider modifying your architecture to avoid mangling the Data string in the first place.
Implementing the custom converter deserialization using an ExpandoObject can avoid the nested DTO class if desired:
using System.Dynamic;
.
.
.
public override FileEntity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
dynamic obj = JsonSerializer.Deserialize<ExpandoObject>(ref reader, options);
return new FileEntity {
Data = (obj.data == null) ? null : Convert.FromBase64String(obj.data.GetString().Remove(0, obj.data.GetString().IndexOf(',') + 1))
};
}
it makes the custom converter a little more flexible when developing, since the DTO doesn't continually need to grow with the base class being deserialized into. It also makes handling potential nullable properties a bit easier too (over standard JsonElement deserialization, i.e. JsonSerializer.Deserialize< JsonElement >) as such:
public override FileEntity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
dynamic obj = JsonSerializer.Deserialize<ExpandoObject>(ref reader, options);
return new FileEntity {
SomeNullableInt32Property = obj.id?.GetInt32(),
Data = (obj.data?.GetString() == null) ? null : Convert.FromBase64String(obj.data.GetString().Remove(0, obj.data.GetString().IndexOf(',') + 1))
};
}
Suppose I have an object with
private Double test;
// Need specific output in JSON via Jackson: test = 24.6000
When output to JSON via Jackson, I get 24.6, but I need the exact 4-decimal output as in the example. Does Jackson allow this?
For example, for Dates, we found a way to force MM/dd/yyyy:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM/dd/yyyy")
Date myDate;
We need something similar for Decimal formatting.
One way of doing this is to use custom json serializer and specify in #JsonSerialize.
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
jgen.writeNull();
} else {
final String pattern = ".####";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}
You can try to use com.fasterxml.jackson.databind.util.RawValue:
BigDecimal d = new BigDecimal(new BigInteger("246000"), 4);
RawValue rv = new RawValue(d.toPlainString());
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode output = objectMapper.createObjectNode();
output.putRawValue("decimal_value", rv);
System.out.println(output.toPrettyString());
//output is:
//{
// "decimal_value" : 24.6000
//}
I have looked a lot but still couldn't get the answer so far, any help is really appreciated!
I have a simple String to Date field mapping and try to read a JSON string to Java object.
#JsonInclude(value=Include.NON_EMPTY)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MMM-yyyy", timezone="PST")
protected Date eolAnnounceDate;
However I am getting the following exception if the JSON string value is empty.
Can you someone tell me how to get around this? I have tried a few options but they are all for serialization.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
Exception :
java.lang.IllegalArgumentException: Failed to parse Date value 'NULL' (format: "dd-MMM-yyyy"): Unparseable date: "NULL"
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:180)
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:279)
com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:260)
com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:464)
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:295)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:230)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207)
com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:464)
com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:295)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2034)
com.cisco.cre.dao.impl.ElasticsearchDAOImpl.getListByIdsFilter(ElasticsearchDAOImpl.java:94)
Thanks
- Atul
Your problem is not that a null value is passed in the JSON. The problem is that the JSON contains a string that has the value "NULL".
So, in order to fix this there are a number of available approaches. I think that the following two will work for this case.
Alternative 1: Fix the JSON
The first alternative is to fix the JSON so that it does not contain the the string value "NULL" and instead contain the value null (not quoted) or simply skip it.
Imagine the following POJO:
public class DatePojo {
#JsonInclude(value= JsonInclude.Include.NON_EMPTY)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MMM-yyyy", timezone="PST")
#JsonProperty("date")
private Date date;
}
The following test shows that valid dates, empty values and null values work:
#Test
public void testJson() throws IOException {
String jsonWithValidDate = "{\"date\":\"12-Jun-1982\"}";
String jsonWithNoDate = "{}";
String jsonWithNullDate = "{\"date\":null}";
ObjectMapper mapper = new ObjectMapper();
final DatePojo pojoWithValidDate = mapper.readValue(jsonWithValidDate, DatePojo.class);
final DatePojo pojoWithNoDate = mapper.readValue(jsonWithNoDate, DatePojo.class);
final DatePojo pojoWithNullDate = mapper.readValue(jsonWithNullDate, DatePojo.class);
Assert.assertNotNull(pojoWithValidDate.date);
Assert.assertNull(pojoWithNoDate.date);
Assert.assertNull(pojoWithNullDate.date);
}
However, if you pass along the value "NULL" the test fails since "NULL" can not be parsed as a date:
#Test(expected = JsonMappingException.class)
public void testInvalidJson() throws IOException {
String jsonWithNullString = "{\"date\":\"NULL\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonWithNullString, DatePojo.class); // Throws the exception
Assert.fail();
}
Alternative 2: provide your own converter that handles "NULL"
If it is not possible to fix the JSON (as described in alternative 1) you can provide your own converter.
Setup your pojo like this instead:
public class DatePojo {
#JsonProperty("date")
#JsonDeserialize(converter = MyDateConverter.class)
private Date date;
}
And provide a converter along the lines of:
public class MyDateConverter extends StdConverter<String, Date> {
#Override
public Date convert(final String value) {
if (value == null || value.equals("NULL")) {
return null;
}
try {
return new SimpleDateFormat("dd-MMM-yyyy").parse(value);
} catch (ParseException e) {
throw new IllegalStateException("Unable to parse date", e);
}
}
}
Then, you should be all set. The following test passes:
#Test
public void testJson() throws IOException {
String jsonWithValidDate = "{\"date\":\"12-Jun-1982\"}";
String jsonWithNoDate = "{}";
String jsonWithNullDate = "{\"date\":null}";
String jsonWithNullString = "{\"date\":\"NULL\"}"; // "NULL"
ObjectMapper mapper = new ObjectMapper();
final DatePojo pojoWithValidDate = mapper.readValue(jsonWithValidDate, DatePojo.class);
final DatePojo pojoWithNoDate = mapper.readValue(jsonWithNoDate, DatePojo.class);
final DatePojo pojoWithNullDate = mapper.readValue(jsonWithNullDate, DatePojo.class);
final DatePojo pojoWithNullStr = mapper.readValue(jsonWithNullString, DatePojo.class); // Works
Assert.assertNotNull(pojoWithValidDate.date);
Assert.assertNull(pojoWithNoDate.date);
Assert.assertNull(pojoWithNullDate.date);
Assert.assertNull(pojoWithNullStr.date); // Works
}
IMO, the best approach is to use alternative 1 where you simply change the JSON.
I reflected the JSON.NET JavaScriptDateTimeConverter class code, copied it, and renamed the class AS3DateTimeConverter so that I could modify it to format DateTime objects in a more precise and strongly-typed manor.
I have it output a type according to how JSON.NET outputs strongly-typed objects like so:
{"$type":"System.DateTime, mscorlib","ticks":0}
The overridden WriteJson method of the JsonConverter runs to produce that value.
However, when I try to deserialize the string using the exact same settings with the same converter, the overridden ReadJson method never gets a chance to run and construct a DateTime from the ticks property, because the following errors occurs:
Cannot deserialize the current JSON object (e.g. {"name":"value"})
into type 'System.DateTime' because the type requires a JSON primitive
value (e.g. string, number, boolean, null) to deserialize correctly.
To fix this error either change the JSON to a JSON primitive value
(e.g. string, number, boolean, null) or change the deserialized type
so that it is a normal .NET type (e.g. not a primitive type like
integer, not a collection type like an array or List) that can be
deserialized from a JSON object. JsonObjectAttribute can also be added
to the type to force it to deserialize from a JSON object.
Path 'ticks', line 1, position 45.
Is this some kind of bug or limitation, which will not allow me to revive a DateTime type because it is a value-type? Or am I missing something?
Here are the serialization settings:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add( new AS3DateTimeConverter() );
//settings.Binder = new AS3SerializationBinder();
string s = JsonConvert.SerializeObject( new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ), settings );
object o = JsonConvert.DeserializeObject( s, settings ); //s = "{\"$type\":\"System.DateTime, mscorlib\",\"ticks\":0}" //ERROR OCCURS HERE
The problem seems to have something to do with deserializing a bare date. When the Date is wrapped in another object, it seems to work. This code works for me:
public class Program
{
public static void Main(string[] args)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add(new AS3DateTimeConverter());
TestObject obj = new TestObject { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
string s = JsonConvert.SerializeObject(obj, settings);
Console.WriteLine(s);
object o = JsonConvert.DeserializeObject(s, settings);
Console.WriteLine(((TestObject)o).Date.ToString());
}
}
public class TestObject
{
public DateTime Date { get; set; }
}
public class AS3DateTimeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
jo.Add("$type", "System.DateTime, mscorlib");
jo.Add("ticks", ((DateTime)value).Ticks);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new DateTime(jo["ticks"].Value<long>());
}
}
Output:
{"$id":"1","$type":"Q20224027.TestObject, JsonTest","Date":{"$type":"System.DateTime, mscorlib","ticks":621355968000000000}}
1/1/1970 12:00:00 AM
UPDATE
To test the theory of whether converters get called for custom top level objects with embedded type information, I made a converter for the date wrapper object and serialized that instead. This worked, but only if I gave it a hint by using DeserializeObject<T> instead of DeserializeObject. Here is the code:
namespace Q20224027
{
public class Program
{
public static void Main(string[] args)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add(new DateWrapperConverter());
DateWrapper obj = new DateWrapper { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
string s = JsonConvert.SerializeObject(obj, settings);
Console.WriteLine(s);
object o = JsonConvert.DeserializeObject<DateWrapper>(s, settings);
Console.WriteLine(((DateWrapper)o).Date.ToString());
}
}
public class DateWrapper
{
public DateTime Date { get; set; }
}
public class DateWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateWrapper);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateWrapper obj = (DateWrapper)value;
JObject jo = new JObject();
jo.Add("$type", typeof(DateWrapper).AssemblyQualifiedName);
jo.Add("ticks", obj.Date.Ticks);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new DateWrapper { Date = new DateTime(jo["ticks"].Value<long>()) };
}
}
}
Output:
{"$type":"Q20224027.DateWrapper, JsonTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","ticks":621355968000000000}
1/1/1970 12:00:00 AM
Workaround I found.
If the object is wrapped in a list before being serialized, then it works ok, but only if you decorate the class with a JsonConverter attribute specifying the converter. Adding the converter to the list of converters for the serializers settings is insufficient.
For example, if you have a "Node" class which has a "Child" Node member (i.e. the type has members of it's own type), and you nest some nodes, then what I've found is that the converter is not called during serialization for anything but the top node when you only add the converter to the list of converters. On the other hand, if you explicitly decorate the class with the converter, then all the child nodes are run through the converter's WriteJson method as expected. So this basically renders the "Converters" collection of the serializer's settings non-functional.
When the objects are members of an array and their type is decorated with an explicit converter, then their converter's ReadJson and WriteJson methods are called when the types are encountered during serialization and deserialization.
When receiving a JSON string from a client, there are only two options to get it to work. Either you manually wrap the string in an object including a generic List "$type" and embed the received value as the sole value in the "$values" array, or you must just avoid all this and hard-code the expected recieved object type by calling typed DeserializeObject<T> method. What a mess.
The only reason I can fathom any of this making sense is if the DeserializeObject (non-generic) method was explicitly intended to NOT call converters for the top-level object, presumably so that it could be used in custom converter's WriteJson method without causing recursive calls to the converter. If that's the case, the design is terrible, because it leads to all the problems I've discussed.
I am using Json.net in my MVC 4 program.
I have an object item of class Item.
I did:
string j = JsonConvert.SerializeObject(item);
Now I want to add an extra property, like "feeClass" : "A" into j.
How can I use Json.net to achieve this?
You have a few options.
The easiest way, as #Manvik suggested, is simply to add another property to your class and set its value prior to serializing.
If you don't want to do that, the next easiest way is to load your object into a JObject, append the new property value, then write out the JSON from there. Here is a simple example:
class Item
{
public int ID { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Item item = new Item { ID = 1234, Name = "FooBar" };
JObject jo = JObject.FromObject(item);
jo.Add("feeClass", "A");
string json = jo.ToString();
Console.WriteLine(json);
}
}
Here is the output of the above:
{
"ID": 1234,
"Name": "FooBar",
"feeClass": "A"
}
Another possibility is to create a custom JsonConverter for your Item class and use that during serialization. A JsonConverter allows you to have complete control over what gets written during the serialization process for a particular class. You can add properties, suppress properties, or even write out a different structure if you want. For this particular situation, I think it is probably overkill, but it is another option.
Following is the cleanest way I could implement this
dynamic obj = JsonConvert.DeserializeObject(jsonstring);
obj.NewProperty = "value";
var payload = JsonConvert.SerializeObject(obj);
You could use ExpandoObject.
Deserialize to that, add your property, and serialize back.
Pseudocode:
Expando obj = JsonConvert.Deserializeobject<Expando>(jsonstring);
obj.AddeProp = "somevalue";
string addedPropString = JsonConvert.Serializeobject(obj);
I think the most efficient way to serialize a property that doesn't exist in the type is to use a custom contract resolver. This avoids littering your class with the property you don't want, and also avoids the performance hit of the extra serialization round trip that most of the other options on this page incur.
public class SpecialItemContractResolver : DefaultContractResolver {
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
var list = base.CreateProperties(type, memberSerialization);
if (type.Equals(typeof(Item))) {
var feeClassProperty = CreateFeeClassProperty();
list.Add(feeClassProperty);
}
return list;
}
private JsonProperty CreateFeeClassProperty() {
return new JsonProperty {
PropertyName = "feeClass",
PropertyType = typeof(string),
DeclaringType = typeof(Item),
ValueProvider = new FeeClassValueProvider(),
AttributeProvider = null,
Readable = true,
Writable = false,
ShouldSerialize = _ => true
};
}
private class FeeClassValueProvider : IValueProvider {
public object GetValue(object target) => "A";
public void SetValue(object target, object value) { }
}
}
To use this functionality:
// This could be put in a static readonly place so it's reused
var serializerSettings = new JsonSerializerSettings {
ContractResolver = new SpecialItemContractResolver()
};
// And then to serialize:
var item = new Item();
var json = JsonConvert.Serialize(item, serializerSettings);