I'm attempting to use Json.NET with the System.Net.Http.HttpClient to send an object with an enum property, however the enum is always serialized as an integer value rather than the string equivalent.
I've tried following the instructions here:
http://james.newtonking.com/archive/2013/05/08/json-net-5-0-release-5-defaultsettings-and-extension-data
By both adding an instance of StringEnumConverter to the JsonSerializerSettings and also tried to decorate the enum property with [JsonProperty(ItemConverterType = typeof(StringEnumConverter))] neither of which appear to be working in my example.
I'm using Json.NET version 5.0.8
Can anyone tell me what I'm doing wrong please? Here is a sample console app to replicate showing both the global serializer settings and the decorated property:
Thanks.
using System;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
HttpClient client = new HttpClient { BaseAddress = new Uri("http://test-uri.com") };
JsonConvert.DefaultSettings = (() =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
return settings;
});
var data = new TestData { Enum = TestEnum.Hello };
// The following posts: {"Enum":0}
// Shouldn't it post {"Enum":"Hello"} instead?
var result = client.PostAsJsonAsync("/test", data).Result;
}
public class TestData
{
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public TestEnum Enum { get; set; }
}
public enum TestEnum
{
Hello,
World
}
}
}
I've inspected this with Fiddler and it posts: {"Enum":0} rather than {"Enum":"Hello"} which is what I would expect.
The ItemConverterType property of the JsonPropertyAttribute attribute is the converter to use for items of a collection. You should be using the JsonConverterAttribute attribute.
public class TestData
{
[JsonConverter(typeof(StringEnumConverter))]
public TestEnum Enum { get; set; }
}
I think I've found a way of getting it to work without decorating attributes. It involves replacing client.PostAsJsonAsync() with client.PostAsync(). You can then specify the MediaTypeFormatter to use which in this case will be the JsonMediaTypeFormatter.
Source here: .net HttpClient with custom JsonConverter
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());
var formatter = new JsonMediaTypeFormatter { SerializerSettings = settings };
var response = client.PostAsync("/test", data, formatter).Result;
This still doesn't explain why the DefaultSettings aren't being applied. I can only assume that the presence of the [JsonConverter] property forces the HttpClient to use Json.NET for serialization, otherwise it just uses the default serializer.
Related
We are working on a .Net core based web api application and for this we have a requirement to validate incoming request body which is JSON format against the c# based type.
We are at this point evaluating NJsonSchema library to see if it throws duplicate property error.
But looks like it doesnt support this validation. We also checked JSON schema validator from NewtonSoft but seems like it doesnt support duplicate property validations either.
Below is the minimized code using NJsonSchema that we use -
using NewtonSoft.Json;
public class MyRequest
{
[JsonRequired]
[JsonProperty("name")]
public string Name { get; set; }
}
and when we pass a JSON object like this -
{"name":"abc","name":"xyz"}
We need our JSON validator to throw error for duplicate property
Our example test looks like this -
[Test]
public async System.Threading.Tasks.Task SchemaValidation_WithDuplicateProperty_Async()
{
var jsonString = await File.ReadAllTextAsync("Data//JsonWithDuplicateProperty.json");
var schema = JsonSchema.FromType<MyRequest>();
var errors = schema.Validate(jsonString);
Assert.That(errors.Count(), Is.EqualTo(1));
}
So my question - Has anyone done this in the past? Or are there any libraries for .net core that provides JSON validation for duplicate properties and/or can this be done using NJsonSchema or NewtonSoft.
As #zaggler notes, using Newtonsoft, you can use the DuplicatePropertyNameHandling enum. Unfortunately, you can't use it directly in a a call to DeserializeObject (in the JsonSerializerSettings); it has to be used in a JToken Reader. See this discussion thread for more details:
https://github.com/JamesNK/Newtonsoft.Json/issues/931
Here is a method that wraps the action up in a DeserializeObject-esque way:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
public class Program
{
public static void Main()
{
var json = #"{""name"":""abc"",""name"":""xyz""}";
var objA = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Ignore);
Console.WriteLine(".Ignore: " + objA.Name);
var objB = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Replace);
Console.WriteLine(".Replace: " + objB.Name);
var objC = DeserializeObject<MyRequest>(json, new JsonSerializerSettings(), DuplicatePropertyNameHandling.Error);
Console.WriteLine(".Error: " + objC.Name); // throws error before getting here
}
public static T DeserializeObject<T>(string json, JsonSerializerSettings settings, DuplicatePropertyNameHandling duplicateHandling)
{
JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings);
using (var stringReader = new StringReader(json))
using (var jsonTextReader = new JsonTextReader(stringReader))
{
jsonTextReader.DateParseHandling = DateParseHandling.None;
JsonLoadSettings loadSettings = new JsonLoadSettings {
DuplicatePropertyNameHandling = duplicateHandling
};
var jtoken = JToken.ReadFrom(jsonTextReader, loadSettings);
return jtoken.ToObject<T>(jsonSerializer);
}
}
}
public class MyRequest
{
[JsonRequired]
[JsonProperty("name")]
public string Name { get; set; }
}
Output:
.Ignore: abc
.Replace: xyz
Run-time exception (line 31): Property with
the name 'name' already exists in the current JSON object. Path
'name', line 1, position 21.
See:
https://dotnetfiddle.net/EfpzZu
I have upgraded my version of .Net Core from preview 2 to preview 6 which has broken a couple of things. Most significant is that I cannot use newtonsoft JSON anymore.
AddNewtonsoftJson in ConfigureServices seemingly does nothing, and the new Json serializer seems to work on properties only, not fields. It does not see the JSONIgnoreAttribute.
In ConfigureServices (in Startup) I have the line
services.AddMvc(x => x.EnableEndpointRouting = false).AddNewtonsoftJson();
which doesn't seem to be doing what it should. In my application, only properties are serialized, not fields, and the [JSONIgnore] attribute does nothing.
The lack of fields I can work around by promoting all the public fields I need to be properties, but I need to be able to ignore some.
Has anyone else had this? How do I either get the new JSON serializer to ignore some properties and serialize public fields, or go back to Newtonsoft?
System.Text.Json has a JsonIgnore attribute, please see How to ignore properties with System.Text.Json.
In order for it to work you will need to remove the dependency on Newtonsoft.Json and change the namespaces in relevant files to System.Text.Json.Serialization;
Sytem.Text.Json can include fields, but only public ones.
using System.Text.Json;
using System.Text.Json.Serialization;
var json = JsonSerializer.Serialize(new O(), new JsonSerializerOptions() { WriteIndented = true});
Console.WriteLine(json);
class O {
[JsonInclude]
public int publicField = 1;
//[JsonInclude]
//This won't work and throws an exception
//'The non-public property 'privateField' on type 'O' is annotated with 'JsonIncludeAttribute' which is invalid.'
private int privateField = 2;
[JsonIgnore]
public int P1 { get; set;} = 3;
public int P2 { get; set; } = 4;
}
This results in:
{
"P2": 4,
"publicField": 1
}
Alternatively you can use IncludeFields
var json = JsonSerializer.Serialize(new O(), new JsonSerializerOptions() { IncludeFields = true});
(Reference: Include fields)
I have the following code:
return (DataTable)JsonConvert.DeserializeObject(_data, (typeof(DataTable)));
Then, I tried:
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
return (DataTable)JsonConvert.DeserializeObject<DataTable>(_data, jsonSettings);
The return line is throwing the error:
{"Error converting value \"\" to type 'System.Double'."}
Lots of solutions online suggesting creating custom Class with nullable types but this won't work for me. I can't expect the json to be in a certain format. I have no control over the column count, column type, or column names.
You can supply settings to JsonConvert.DeserializeObject to tell it how to handle null values, in this case, and much more:
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
var jsonModel = JsonConvert.DeserializeObject<Customer>(jsonString, settings);
An alternative solution for Thomas Hagström, which is my prefered, is to use the property attribute on the member variables.
For example when we invoke an API, it may or may not return the error message, so we can set the NullValueHandling property for ErrorMessage:
public class Response
{
public string Status;
public string ErrorCode;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string ErrorMessage;
}
var response = JsonConvert.DeserializeObject<Response>(data);
The benefit of this is to isolate the data definition (what) and deserialization (use), the deserilazation needn’t to care about the data property, so that two persons can work together, and the deserialize statement will be clean and simple.
You can subscribe to the 'Error' event and ignore the serialization error(s) as required.
static void Main(string[] args)
{
var a = JsonConvert.DeserializeObject<DataTable>("-- JSON STRING --", new JsonSerializerSettings
{
Error = HandleDeserializationError
});
}
public static void HandleDeserializationError(object sender, ErrorEventArgs errorArgs)
{
var currentError = errorArgs.ErrorContext.Error.Message;
errorArgs.ErrorContext.Handled = true;
}
ASP.NET CORE:
The accepted answer works perfectly. But in order to make the answer apply globally, in startup.cs file inside ConfigureServices method write the following:
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
});
The answer has been tested in a .Net Core 3.1 project.
I am currently working on a project that requires me to output XML from its endpoints along with JSON. I have the following model:
[DataContract(Namespace="http://www.yale.edu/tp/cas")]
[XmlType("serviceResponse")]
[XmlRoot(Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
[XmlElement("authenticationSuccess")]
public AuthenticationSuccess Success { get; set; }
[XmlElement("authenticationFailure")]
public AuthenticationFailure Failure { get; set; }
}
The output is as follows when success is not null:
<serviceResponse>
<authenticationSuccess />
</serviceResponse>
Now, I can see that obviously, I don't have a prefix assigned to the namespace I told the elements to be a part of. My issue is that I cannot find a place to add the namespace prefixes in MVC4 using the media formatter. I have the following in my global.asax:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
GlobalConfiguration.Configuration.Formatters.XmlFormatter.RemoveSerializer(typeof(Models.ServiceResponse));
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer(typeof(Models.ServiceResponse), new Infrastructure.NamespaceXmlSerializer(typeof(Models.ServiceResponse)));
I made a custom serializer based on XmlSerializer in an attempt to intercept the writing request and tack the namespace list on there. The issue with this method is that right now I have breakpoints inside every overrideable method and none of them are tripped when serializing which leads me to believe that my serializer isn't being used.
Is there some built in way to accomplish what I want to do or am I stuck re-implementing the XmlMediaTypeFormatter to pass in namespaces when it serializes objects?
As a followup answer: The easiest solution for me was to write my own XmlMediaTypeFormatter. As it turns out, its not nearly as intimidating as I thought.
public class NamespacedXmlMediaTypeFormatter : XmlMediaTypeFormatter
{
const string xmlType = "application/xml";
const string xmlType2 = "text/xml";
public XmlSerializerNamespaces Namespaces { get; private set; }
Dictionary<Type, XmlSerializer> Serializers { get; set; }
public NamespacedXmlMediaTypeFormatter()
: base()
{
this.Namespaces = new XmlSerializerNamespaces();
this.Serializers = new Dictionary<Type, XmlSerializer>();
}
public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
lock (this.Serializers)
{
if (!Serializers.ContainsKey(type))
{
var serializer = new XmlSerializer(type);
//we add a new serializer for this type
this.Serializers.Add(type, serializer);
}
}
return Task.Factory.StartNew(() =>
{
XmlSerializer serializer;
lock (this.Serializers)
{
serializer = Serializers[type];
}
var xmlWriter = new XmlTextWriter(writeStream, Encoding.UTF8);
xmlWriter.Namespaces = true;
serializer.Serialize(xmlWriter, value, this.Namespaces);
});
}
}
Here is the formatter as a gist: https://gist.github.com/kcuzner/eef239003d4f99dfacea
The formatter works by exposing the XmlSerializerNamespaces that the XmlSerializer is going to use. This way I can add arbitrary namespaces as needed.
My top model looks as follows:
[XmlRoot("serviceResponse", Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
[XmlElement("authenticationSuccess")]
public CASAuthenticationSuccess Success { get; set; }
[XmlElement("authenticationFailure")]
public CASAuthenticationFailure Failure { get; set; }
}
In my Global.asax I added the following to place my formatter on the top of the list:
var xmlFormatter = new Infrastructure.NamespacedXmlMediaTypeFormatter();
xmlFormatter.Namespaces.Add("cas", "http://www.yale.edu/tp/cas");
GlobalConfiguration.Configuration.Formatters.Insert(0, xmlFormatter);
After adding the formatter and making sure my attributes were set up properly, my XML was properly namespaced.
In my case, I needed to add the cas namespace linking to http://www.yale.edu/tp/cas. For others using this, just change/replicate the Add call to your heart's content adding namespaces.
In Nancy, is there a way to bind the content of a POST request to a dynamic type?
For example:.
// sample POST data: { "Name": "TestName", "Value": "TestValue" }
// model class
public class MyClass {
public string Name { get; set; }
public string Value { get; set; }
}
// NancyFx POST url
Post["/apiurl"] = p => {
// this binding works just fine
var stronglyTypedModel = this.Bind<MyClass>();
// the following bindings do not work
// there are no 'Name' or 'Value' properties on the resulting object
dynamic dynamicModel1 = this.Bind();
var dynamicModel2 = this.Bind<dynamic>();
ExpandoObject dynamicModel3 = this.Bind();
var dynamicModel4 = this.Bind<ExpandoObject>();
}
Out of the box Nancy does not support dynamic model binding. TheCodeJunkie has written a quick ModelBinder to achieve that tho.
https://gist.github.com/thecodejunkie/5521941
Then you can use it like so
dynamic model = this.Bind<DynamicDictionary>();
As the previous answers point, there is no support for binding directly to a dynamic type, the most similar one is the ModelBinder provided by TheCodeJunkie in https://gist.github.com/thecodejunkie/5521941
However, this approach has a problem, and is that the DynamicDictionary resulting from this code does not serialize later properly, producing only the keys of the dictionary and losing the values. This is described here Why does storing a Nancy.DynamicDictionary in RavenDB only save the property-names and not the property-values? and as of today (version 1.4.3) is still happening, limiting seriously this approach.
The solution is to use a simple trick, accessing the raw data received in the POST and deserializing using JSON.Net. In your example it would be:
using System;
using System.Dynamic;
using Nancy;
using Nancy.Extensions;
using Newtonsoft.Json;
Post["/apiurl"] = p => {
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(Request.Body.AsString());
//Now you can access the object using its properties
return Response.AsJson((object)new { a = obj.Prop1 });
}
Note that you need to use Nancy.Extensions for the Request.Body.AsString() call.
I was looking for a way to deserialize my POST body as dynamic and found this question, I’ll put my solution using Newtonsoft and extension method just in case that result useful for somebody else.
Extension method
using System.IO;
using Nancy;
using Newtonsoft.Json;
namespace NancyFx
{
public static class DynamicModelBinder
{
public static dynamic ToDynamic(this NancyContext context)
{
var serializer = new JsonSerializer();
using (var sr = new StreamReader(context.Request.Body))
{
using (var jsonTextReader = new JsonTextReader(sr))
{
return serializer.Deserialize(jsonTextReader);
}
}
}
}
}
Usage
using Nancy;
using Nancy.ModelBinding;
namespace NancyFx
{
public class HomeModule : NancyModule
{
public HomeModule(IAppConfiguration appConfig)
{
Post("/product", args => {
dynamic product = Context.ToDynamic();
string name = product.Name;
decimal price = product.Price;
return Response.AsJson(new {IsValid=true, Message= "Product added sucessfully", Data = new {name, price} });
});
}
}
}
I'm not sure, but you can try:
dynamic model = new ExpandoObject();
model = Request; //or Request.Form
return View["ViewName", model];
let me know if works :)