Converting simple value to JSON in ASP.NET Core API - json

Sometimes my ASP.NET Core API needs to return a simple value i.e. bool, int or string even though in most cases, I return complex objects/arrays as JSON.
I think for consistency purposes, it's a good idea to return even simple values as JSON. What's the easiest way to convert a simple value, whether it's bool or int into JSON?
My standard controller action looks like this -- see below -- which gives me the ability to return status codes as well as data. Therefore, I'd like to stick to that approach, rather than return JsonResult.
public async Task<IActionResult> Get()
{
// Some logic
return Ok(data);
}
I'm just trying to figure out the easiest way to convert my data into JSON, if it's not already in JSON format.

Looking at your code, I assume your application is supposed to be a service that needs to return some kind of data serialised in JSON.
Well, good news is ASP.NET Core already includes a data serialiser that would do the job for you.
You may need to set it up according to your needs.
For example, let's assume the following data class:
public class Data {
public string Name { get; }
public string Value { get; }
public bool IsValid { get; }
public Data(string name, string value, bool isValid) {
Name = name;
Value = value;
IsValid = isValid;
}
}
Then the following method in your Controller:
public async Task<IActionResult> Get() {
var data = new Data("sample name", "this is a value", true);
return Ok(data);
}
would return:
{
"name": "sample name",
"value": "this is a value",
"isValid": true
}
Even thought the standard serialisation behaviour may fit fine for very simple implementations, you may need more control on how your different data types should be serialised (and deserialised) by your application, especially when those do not exactly match the way you want to present the data back to the client. In this case you may want to use Custom Converters.
You can configure that when setting up MVC in the ConfigureServices(IServiceCollection services) method:
// Add framework services.
services.AddMvc().AddJsonOptions(jo => {
// sample serialiser setup
jo.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jo.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
jo.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
// custom Converters
jo.SerializerSettings.Converters.Add(new MyCustomConverter());
});
Here you can read and learn more on how to setup and use Custom Converters.

Related

How to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON?

I've got following setup: C#, ServiceStack, MariaDB, POCOs with objects and structs, JSON.
The main question is: how to use ServiceStack to store POCOs to MariaDB having complex types (objects and structs) blobbed as JSON and still have working de/serialization of the same POCOs? All of these single tasks are supported, but I had problems when all put together mainly because of structs.
... finally during writing this I found some solution and it may look like I answered my own question, but I still would like to know the answer from more skilled people, because the solution I found is a little bit complicated, I think. Details and two subquestions arise later in the context.
Sorry for the length and for possible misinformation caused by my limited knowledge.
Simple example. This is the final working one I ended with. At the beginning there were no SomeStruct.ToString()/Parse() methods and no JsConfig settings.
using Newtonsoft.Json;
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite;
using ServiceStack.Text;
using System.Diagnostics;
namespace Test
{
public class MainObject
{
public int Id { get; set; }
public string StringProp { get; set; }
public SomeObject ObjectProp { get; set; }
public SomeStruct StructProp { get; set; }
}
public class SomeObject
{
public string StringProp { get; set; }
}
public struct SomeStruct
{
public string StringProp { get; set; }
public override string ToString()
{
// Unable to use .ToJson() here (ServiceStack does not serialize structs).
// Unable to use ServiceStack's JSON.stringify here because it just takes ToString() => stack overflow.
// => Therefore Newtonsoft.Json used.
var serializedStruct = JsonConvert.SerializeObject(this);
return serializedStruct;
}
public static SomeStruct Parse(string json)
{
// This method behaves differently for just deserialization or when part of Save().
// Details in the text.
// After playing with different options of altering the json input I ended with just taking what comes.
// After all it is not necessary, but maybe useful in other situations.
var structItem = JsonConvert.DeserializeObject<SomeStruct>(json);
return structItem;
}
}
internal class ServiceStackMariaDbStructTest
{
private readonly MainObject _mainObject = new MainObject
{
ObjectProp = new SomeObject { StringProp = "SomeObject's String" },
StringProp = "MainObject's String",
StructProp = new SomeStruct { StringProp = "SomeStruct's String" }
};
public ServiceStackMariaDbStructTest()
{
// This one line is needed to store complex types as blobbed JSON in MariaDB.
MySqlDialect.Provider.StringSerializer = new JsonStringSerializer();
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
}
public void Test_Serialization()
{
try
{
var json = _mainObject.ToJson();
if (!string.IsNullOrEmpty(json))
{
var objBack = json.FromJson<MainObject>();
}
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void Test_Save()
{
var cs = "ConnectionStringToMariaDB";
var dbf = new OrmLiteConnectionFactory(cs, MySqlDialect.Provider);
using var db = dbf.OpenDbConnection();
db.DropAndCreateTable<MainObject>();
try
{
db.Save(_mainObject);
var dbObject = db.SingleById<MainObject>(_mainObject.Id);
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
}
What (I think) I know / have tried but at first didn't help to solve it myself:
ServiceStack stores complex types in DB as blobbed JSV by default (last paragraph of first section: https://github.com/ServiceStack/ServiceStack.OrmLite), so it is necessary to set it the way it is proposed: MySqlDialect.Provider.StringSerializer = new JsonStringSerializer(); (https://github.com/ServiceStack/ServiceStack.OrmLite#pluggable-complex-type-serializers)=> default JSV changed to JSON.
the ServiceStack's serialization does not work with structs, it is necessary to treat them special way:
a) according to https://github.com/ServiceStack/ServiceStack.Text#c-structs-and-value-types and example https://github.com/ServiceStack/ServiceStack.Text/#using-structs-to-customize-json it is necessary to implement TStruct.ToString() and static TStruct.ParseJson()/ParseJsv() methods.
b) according to https://github.com/ServiceStack/ServiceStack.Text/#typeserializer-details-jsv-format and unit tests https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/CustomStructTests.cs it shall be TStruct.ToString() (the same as in a) and static TStruct.Parse().
Subquestion #1: which one is the right one? For me, ParseJson() was never called, Parse() was. Documentation issue or is it used in other situation?
I implemented option b). Results:
IDbConnection.Save(_mainObject) saved the item to MariaDB. Success.
Through the saving process ToString() and Parse() were called. In Parse, incoming JSON looked this way:
"{\"StringProp\":\"SomeStruct's String\"}". Fine.
Serialization worked. Success.
Deserialization failed. I don't know the reason, but JSON incoming to Parse() was "double-escaped":
"{\\\"StringProp\\\":\\\"SomeStruct's String\\\"}"
Subquestion #2: Why the "double-escaping" in Parse on deserialization?
I tried to solve structs with JsConfig (and Newtonsoft.Json to get proper JSON):
JsConfig<SomeStruct>.SerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.DeSerializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) at first without ToString() and Parse() defined in the TStruct. Results:
Save failed: the json input in JsonConvert.DeserializeObject(json) that is used during Save was just type name "WinAmbPrototype.SomeStruct".
De/serialization worked.
b) then I implemented ToString() also using Newtonsoft.Json. During Save ToString() was used instead of JsConfig.SerializeFn even the JsConfig.SerializeFn was still set (maybe by design, I do not judge). Results:
Save failed: but the json input of DeserializeFn called during Save changed, now it was JSV-like "{StringProp:SomeStruct's String}", but still not deserializable as JSON.
De/serialization worked.
Then (during writing this I was still without any solution) I found JsConfig.Raw* "overrides" and tried them:
JsConfig<SomeStruct>.RawSerializeFn = someStruct => JsonConvert.SerializeObject(someStruct);
JsConfig<SomeStruct>.RawDeserializeFn = json => JsonConvert.DeserializeObject<SomeStruct>(json);
a) at first without ToString() and Parse() defined in the TStruct. Results are the same as in 2a.
b) then I implemented ToString(). Results:
BOTH WORKED. No Parse() method needed for this task.
But it is very fragile setup:
if I removed ToString(), it failed (now I understand why, default ToString produced JSON with just type name in 2a, 3a).
if I removed RawSerializeFn setting, it failed in RawDeserializeFn ("double-escaped" JSON).
Is there some simpler solution? I would be very glad if someone points me to better direction.
Acceptable would be maybe two (both of them accessible because of different circumstances):
if I am the TStruct owner: with just pure TStruct.ToString() and static TStruct.Parse() to support out of the box de/serialization and DB by ServiceStack (without different input in Parse()).
if I am a consumer of TStruct with no JSON support implemented and I am without access to its code: until now I did not find the way, if the ToString is not implemented: Save to DB did not work. Maybe would be fine to ensure JsConfig serialize functions are enough for both de/serialization and when used during saving to DB.
And the best one would be without employing other dependency (e.g. Newtonsoft.Json) to serialize structs. Maybe some JsConfig.ShallProcessStructs = true; (WARNING: just a tip, not working as of 2021-04-02) would be fine for such situations.
ServiceStack treats structs like a single scalar value type, just like most of the core BCL Value Types (e.g. TimeSpan, DateTime, etc). Overloading the Parse() and ToString() methods and Struct's Constructor let you control the serialization/deserialization of custom structs.
Docs have been corrected. Structs use Parse whilst classes use ParseJson/ParseJsv
If you want to serialize a models properties I'd suggest you use a class instead as the behavior you're looking for is that of a POCO DTO.
If you want to have structs serailized as DTOs in your RDBMS an alternative you can try is to just use JSON.NET for the complex type serialization, e.g:
public class JsonNetStringSerializer : IStringSerializer
{
public To DeserializeFromString<To>(string serializedText) =>
JsonConvert.DeserializeObject<To>(serializedText);
public object DeserializeFromString(string serializedText, Type type) =>
JsonConvert.DeserializeObject(serializedText, type);
public string SerializeToString<TFrom>(TFrom from) =>
JsonConvert.SerializeObject(from);
}
MySqlDialect.Provider.StringSerializer = new JsonNetStringSerializer();

Returning a Json-field from SQL to ASP.NET Core API

I'm building a relatively simple Get-method in an ASP.NET Core (3+) application. (Currently 3.1 - to be migrated to 5)
The object I need to return looks like this:
public class Data
{
public int ID { get;set;}
public string Name { get;set;}
public string Settings { get; set;}
}
And the Get-method is simply this:
public IActionResult<Data> GetData()
{
var data = _dbContext.GetData<Data>();
return Ok(data);
}
This works perfectly - except for one thing.
In SQL - the settings column (varchar(8000)), contains JSON data. In some cases, a setting can be something simple like : { "threshold": 8754 } and sometimes it can be a large complex object with many fields, but it is always valid Json.
On the ASP side, it does exactly what you would expect. It turns a serialized Json object that contains an INT and 2 x strings.
I would like for it to return an INT, ONE String and One Json Object.
Is there any way that I can tell the serializer that the Settings-property contains Json?
In a perfect world, I would love something like this:
public class Data
{
public int ID { get;set;}
public string Name { get;set;}
[SerializeContentAsJson]
public string Settings { get; set;}
}
Is there a way to do this or is there some other fairly elegant solution to this problem?
Btw. I fully realize that the caller can specify the content types that he/she will accept. In this case, the API is purely for use inside my team and we will always want JSON, so I can compromise on this being a relatively custom solution that might not work if you wanted text/html or some other content type.

How to force DotNetCore 2.1 Web API to output Json format? What library do I need?

I be straight to the point. I am in the process of converting ASP.Net web services into DotNetCore 2.1 services. My question is very simple. How do I get json output from a string (with a GET verb)?
I'm new at this, but almost every piece of documentation and recommendations do not work with DotNetCore.
Obviously, the following will not work:
[HttpGet]
public string Get()
{
return "{\"country_code\":\"US\",\"country_name\":\"United States\",\"region_name\":\"California\",\"city_name\":\"Los Angeles\",\"latitude\":\"34.052230\",\"longitude\":\" - 118.243680\",\"zip_code\":\"90001\",\"time_zone\":\" - 08:00\"}";
}
I just need to convert this string (or tell the client) that I want the output in json. The following does not work either - got a squiggly line under the "Json(" method and, for the life of me, can't find a reference to make it go away (I pulled it from an example, so they must be using a 3rd party json parsing library or there's a reference that I'm missing)
[HttpGet]
public JsonResult Get()
{
return Json("{\"country_code\":\"US\",\"country_name\":\"United States\",\"region_name\":\"California\",\"city_name\":\"Los Angeles\",\"latitude\":\"34.052230\",\"longitude\":\" - 118.243680\",\"zip_code\":\"90001\",\"time_zone\":\" - 08:00\"}", "application/json");
}
Ideally, I'd like to serialize an object to json, but figured I'd start with something ridiculously simple.
Anywho, if anyone can help.
If you don't already have a strongly typed model, you can build an anonymous type and return that from the controller
Simple Example.
public class MyController: Controller {
[HttpGet]
public IActionResult Get() {
var model = new {
country_code = "US",
country_name = "United States",
region_name = "California",
city_name = "Los Angeles",
latitude = 34.052230,
longitude = -118.243680,
zip_code = 90001,
time_zone = "- 08:00"
};
return Ok(model); //200 OK with content
}
}
In more complex scenarios you would get your objects from a data source.
No library needed, the framework out of the box will serialize the object(s) into JSON for you by default unless otherwise configured.
If you insist on passing a manually formatted string then use the ContemntResult object. Pass it the string and the content type.
[HttpGet]
public IActionResult Get() {
string json = "{\"country_code\":\"US\",\"country_name\":\"United States\",\"region_name\":\"California\",\"city_name\":\"Los Angeles\",\"latitude\":\"34.052230\",\"longitude\":\" - 118.243680\",\"zip_code\":\"90001\",\"time_zone\":\" - 08:00\"}";
return Content(json, new MediaTypeHeaderValue("application/json"));
}
Reference Format response data in ASP.NET Core Web API
Forcing a Particular Format
If you would like to restrict the response formats for a specific action you can apply the
[Produces] filter. The [Produces] filter specifies the response
formats for a specific action (or controller). Like most Filters, this
can be applied at the action, controller, or global scope.
[Produces("application/json")]
public class AuthorsController
The [Produces] filter will force all actions within the
AuthorsController to return JSON-formatted responses, even if other
formatters were configured for the application and the client provided
an Accept header requesting a different, available format.
Don't return string but object. So result of your actions are json string this is why you will get string in JSON and not an object
Make sure that your client is sending header "Content-Type": "application/json".
[HttpGet]
public Address Get()
{
return new Address{ CountryCode = "US"} ;
}

Spring MVC Test, MockMVC: Conveniently convert objects to/from JSON

I am used to JAX-RS and would like to have similar comfort when sending requests using Spring MVC and working with the responses, i.e. on the client side inside my tests.
On the server (controller) side I'm quite happy with the automatic conversion, i.e. it suffices to just return an object instance and have JSON in the resulting HTTP response sent to the client.
Could you tell me how to work around the manual process of converting objectInstance to jsonString or vice versa in these snippets? If possible, I'd also like to skip configuring the content type manually.
String jsonStringRequest = objectMapper.writeValueAsString(objectInstance);
ResultActions resultActions = mockMvc.perform(post(PATH)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonStringRequest)
)
String jsonStringResponse = resultActions.andReturn().getResponse().getContentAsString();
Some objectInstanceResponse = objectMapper.readValue(jsonStringResponse, Some.class);
For comparison, with JAX-RS client API I can easily send an object using request.post(Entity.entity(objectInstance, MediaType.APPLICATION_JSON_TYPE) and read the response using response.readEntity(Some.class);
if you have lot's of response objects, you could create some generic JsonToObject mapper-factory. It could be then used to detect the object type from a generic response (all response objects inherit from the same generic class) and respond/log properly from a bad mapping attempt.
I do not have a code example at hand, but as a pseudocode:
public abstract GenericResponse {
public String responseClassName = null;
// get/set
}
In the server code, add the name of the actual response object to this class.
The JsonToObject factory
public ConverterFactory<T> {
private T objectType;
public ConverterFactory(T type) {
objectType = type;
}
public T convert(String jsonString) {
// Type check
GenericResponse genResp = mapper.readValue(result.getResponse().getContentAsString(),
GenericResponse.class);
if (objectType.getClass().getSimpleName().equals(genResp.getResponseClassName())) {
// ObjectMapper code
return mapper.readValue(result.getResponse().getContentAsString(),
objectType.class);
} else {
// Error handling
}
}
}
I think this could be extended to be used with annotation to do more automation magic with the response. (start checking with BeanPostProcessor)
#Component
public class AnnotationWorker implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(final Object bean, String name) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), field -> {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(MyAnnotation.class) != null) {
field.set(bean, log);
}
});
return bean;
}
}
The above code snippet is copied from my current project and it injects to fields, you need to change it so, that it works for methods, eg ... where you may need it.
Having this all implemented may be tricky and can't say it necessarily works even, but it's something to try if you don't mind a bit of educative work.

Add 'standard' JSON fields to serialized objects in REST API

Motivated by this: Google JSON Style Guide, I want to insert a bit of custom serialization logic to my rest API. I'm using the WebAPI 2 and JSON.NET. My goal is to wrap the 'payload' of my response in the 'data' field of the main JSON response, as described in the style guide, include an apiVersion field in every response, and that sort of thing. Of course the controller actions just return straight POCO's, and I want to modify the container that they're sent inside of, not the POCOs themselves, so:
{
"id": "111",
"apiVersion": "1.0",
"data": {
"kind": "monkey",
"name": "manny",
"age": "3"
},
"error": null
}
...that type of thing. So I envision inserting little bits of standard data into every response before it goes over the wire. What's the best way to accomplish this?
TIA.
I believe you can use an ActionFilterAttribute to achieve this kind of behaviour. You would first need to create a class to represent your wrapped response (all the properties are string, adjust as you need):
public class WrappedJsonResponse
{
public string Id {get;set;}
public string ApiVersion {get;set;}
public object Data {get;set;}
public string Error {get;set;}
}
The ActionFilterAttribute allow you to do some processing after the execution of an action via the virtual OnActionExecuted method:
public class WrappedJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext context)
{
// A POCO response will normally be wrapped in an ObjectContent
var content = context.Response.Content as ObjectContent
if(content != null)
{
// Create the WrappedJsonResponse object appropriately and
// put the original result in the Data property
content.Value = new WrappedJsonResponse { Data = content.Value };
content.ObjectType = typeof(WrappedJsonResponse);
}
}
}
With the attribute, you can then choose to apply it where you want (whole controller, action only or as a default filter).
Note: I do not have access to a development environment at the moment and have not tested the filter. If this is not complete, it should at least give you an idea on how it can be done.