Is it possible to make the ServiceStack.Text library throw when attempting to deserialize invalid JSON. By default it looks as if invalid JSON is just ignored, so that the result object contains null values.
When I attempt to deserialize this json (a " is missing after MongoConnectionString)
{
"MongoDb": {
"MongoConnectionString:"mongodb://localhost:27017/x",
"MongoDatabase":"x",
"MongoSafeModeEnabled":true,
"MongoSafeModeFSync":true,
"MongoSafeModeWriteReplicationCount":
"MongoSafeModeWriteTimeout":"00:00:00"
},
by doing this: JsonSerializer.DeserializeFromString(json);
where
public class Configuration {
public class MongoDbSettings
{
public string MongoConnectionString {get;set;}
public string MongoDatabase {get;set;}
public bool MongoSafeModeEnabled {get;set;}
public bool MongoSafeModeFSync {get;set;}
public int MongoSafeModeWriteReplicationCount {get;set;}
public TimeSpan MongoSafeModeWriteTimeout {get;set;}
}
}
I get a Configuration object where MongoDbSettings is null. I would prefer to get an exeception in this case. Is this possible?
At the moment the ServiceStack serializers are optimized for resilience, i.e. deserialize as much as possible without error.
I'd recommend adding some of your own validation checking post serialization to work out which fields weren't deserialized correctly.
You could also submit a pull-request to the ServiceStack.Text project that supports an opt-in flag (i.e. on JsConfig) to change the behavior to throw exceptions.
Related
I'm using a JsonConverter to deal with a polymorphic collection:
class ItemBatch
{
List<ItemBase> Items { get; set; }
}
// For type discrimination of ItemBase
class ItemTypes
{
public int Value { get; set; }
}
[JsonConverter(typeof(ItemConverter))]
abstract class ItemBase
{
public abstract ItemTypes Type { get; set; }
}
class WideItem : ItemBase
{
public override ItemTypes Type => 1;
public decimal Width { get; set; }
}
class HighItem : ItemBase
{
public override ItemTypes Type => 2;
public decimal Height { get; set; }
}
class ItemConverter : JsonConverter<ItemBase>
{
public override ItemBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader));
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
// deserialize depending on type.
}
}
Am using Blazor and store the ItemBatch in IndexedDb before retrieving again later and sending to WebAPI.
Serializing to IndexedDb and deserializing from IndexedDb works fine.
But, when I try to send the ItemBatch to a WebAPI, I get the error:
Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in
System.Text.Json.dll An exception of type
'System.Collections.Generic.KeyNotFoundException' occurred in
System.Text.Json.dll but was not handled in user code The given key
was not present in the dictionary.
From peeking at various values, I suspected an issue with case-sensitivity. Indeed, if I change:
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
to
int type;
try
{
type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
}
catch (Exception)
{
type = jsonDoc.RootElement.GetProperty("type").GetProperty("value").GetInt32();
}
then I get past this error and my WebAPI gets called.
What am I missing that allows me to serialize / deserialize to / from IndexedDb, but the WebApi Json conversion is having issues with case sensitivity.
Newtonsoft was case insensitive.
With System.Text.Json you have to pull some more levers.
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-6-0#case-insensitive-deserialization
Case-insensitive deserialization During deserialization,
Newtonsoft.Json does case-insensitive property name matching by
default. The System.Text.Json default is case-sensitive, which gives
better performance since it's doing an exact match. For information
about how to do case-insensitive matching, see Case-insensitive
property matching.
See this below URL as well:
https://makolyte.com/csharp-case-sensitivity-in-json-deserialization/
Here is a possible way to deal with it:
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-casing
above url is ::: How to enable case-insensitive property name matching with
System.Text.Json
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.getproperty?view=net-6.0#system-text-json-jsonelement-getproperty(system-string)
Remarks
Property name matching is performed as an ordinal, case-sensitive comparison.
I don't think you can overcome that behavior on the "roll your own".
But maybe you can chase this:
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.propertynamecaseinsensitive?view=net-6.0#system-text-json-jsonserializeroptions-propertynamecaseinsensitive
But that seems to be without "roll your own".
My conclusion is that using default serialization, the JsonSerializerOptions do not set a value "CamelCase" for the PropertyNamingPolicy, but the HttpClient and WebApi Request Pipeline do:
PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
This means that when I'm serializing and deserializing to IndexedDb, the default serialization leaves the Json as Pascal Case.
My custom JsonConverter, which was using the options parameter passed into the Read and Write methods, therefore used Pascal case on the client when working with IndexedDb.
However, when the same JsonConverter is called by the HttpClient and the WebApi request pipeline, the options are set to:
PropertyNamePolicy = JsonNamingPolicy.CamelCase.
and when trying to parse to a JsonDocument, the content is now camel cased and my reading of the JsonDocument using Pascal case assumptions then fell over.
The answer to my problem was to update the write method as follows:
public override void Write(Utf8JsonWriter writer, SaleCommandBase value, JsonSerializerOptions options)
{
JsonSerializerOptions newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };
JsonSerializer.Serialize(writer, (object)value, newOptions);
}
This forces the serialization to use Pascal case in all situations, whether that be local serialization on the client (when writing to IndexedDb) or whether serializing within the HttpClient when sending to a WebAPI.
Similary, in the read method:
using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader))
{
int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };
return type switch
{
1 => jsonDoc.RootElement.Deserialize<WideItem>(newOptions),
2 => jsonDoc.RootElement.Deserialize<HighItem>(newOptions),
_ => throw new InvalidOperationException($"Cannot convert type '{type}'."),
};
}
By copying whatever the provided options are, but then overriding the naming policy to use Pascal case (PropertyNamingPolicy = null), then I can be assured that the Json document parsed will always be in Pascal case, regardless of the options provided by the framework.
I need to validate a JSON list similar to the following:
[{"op":"A","path":"C","value":"B"},...]
in a Spring MVC application - I am currently deserializing (using default Jackson) to an object along the lines of:
public class Operations extends ArrayList<Operation>{}
public class Operation {
#NotEmpty
public String op;
#NotEmpty
public String path;
public Object value;
public void setOp(String op)... and other getters/setters
}
but I cannot figure out how to get jsr303 validation provided by reference hibernate implementation to fire for the attributes of Operation.
I can get it to function if I wrap the list in a class but then I have an incorrect format for the JSON, ie something like:
{"ops":[{"op":"A",...},...]}
is it possible to validate the first object (Operations)? and if not is it possible to serialize the first format (ie the JSON list) to an object of the second format (ie a list wrapped in a placeholder object with a placeholder field)
Update
Having failed to find a way to trigger the jsr303 validation on a bare ArrayList I have written a custom jackson json deserializer to stick it into a containing object with an annotated field along the lines of
#JsonDeserialize(using=OperationsDeserializer.class)
public class Operations {
#NotEmpty
private ArrayList<Operation> ops;
public void setOps(ArrayList<Operation>ops)...
public ArrayList<Operation> getOps()...
}
This works but now any autogenerated documentation for my api is generating json examples with the dummy "ops" field in it - ie {"ops" : [ ... ] }
so the search for a method of triggering jsr303 validation on an ArrayList that is not a field of another object continues - perhaps there is a way to inject a proxy wrapping class at runtime that might work around this?
Use ObjectMapper.class. it has a method which will convert Json Object into Class Object
method is , new ObjectMapper().readValue(String str, Class<T> valueType)
So you can iterate your Object array, convert to string and pass it to this method to get your result.
so it would look like,
new ObjectMapper().readValue(object.toString, Operation.class);
I am having a little trouble trying to deserialize an object within a monotouch project using restsharp.
I have this
RestResponse<List<Product>> response = client.Execute<List<Product>> (request) as RestResponse<List<Product>>;
if (response.Data != null) {}
public class Product
{
public Product () {}
[PrimaryKey]
public string Id { get; set; }
public string Name { get; set; }
[Ignore]
public string[] ParentProductIds {
get;
set;
}
I'm getting an error of
Default constructor not found for type System.String[].
My json looks like
[
{
"Id" : "62907011-02f1-440a-92ec-dc35ecf695e0",
"Name" : "ABC",
"ParentProductIds" : ["2cedbcad-576a-4044-b9c7-08872de34a96", "3fcd12ce-8117-4ae7-ae4d-f539e4268e4d"]
},
{
"Id" : "3fcd12ce-8117-4ae7-ae4d-f539e4268e4d",
"Name" : "Name 1",
"ParentProductIds" : null
}
]
Is it due to the null ParentProductId ?
Can anyone advise what I need to do to be able to accept null arrays?
The Key of this issue is that RestSharp uses its own internal Json Serializer/Deserializer, which does not support deserialization of array objects (ParentProductIds in your case). It does support deserialization of List (Generics) objects. Although your solution is completely valid, I believe that there are some scenarios in which one much rather prefer to leave the array instead of using generics. To do so, I continued to use RestSharp for my rest requests, but used JsonConvert from James Newton-King (JSon.Net from NuGet) to deserialize from the response.Content.
Hence you would add Json.Net to your project, add the corresponding using statement and use the following to deserialize your response:
var response = client.Execute(request);
List<Product> product = JsonConvert.DeserializeObject<List<Product>>(response.Content);
The additional benefit is that JSon.Net is a much more efficient serializer, so the code runs in general faster.
Anyone else having the same problem using a List<> instead works fine.
public List<string> ParentProductIds {
get;
set;
I am developing an ASP.Net web api project and I want to validate my Server data model according to the JSON request I get from the Client side. In my Server Model Class, I have a double value and I am sending value from the Client Side as "12,14". I have written a custom validation class which is implemented by ValidationAttribute class of .Net and I am using IsValid(Object value) method to validate this user input.
So when I send my input as "12,14", .Net automatically converts this "12,14" to "1214" by thinking that "," is a group separator. But in this case, "," is not a group separator since this is a valid Double number for Norwaygian culture format ("no" culture).
public class Client : IClient
{
public string ClientId { get; set; }
public int EngagementId { get; set; }
[MyCustomDoubleType]
public double MyValue{ get; set; } //Notice that this is my double value to be validated.
}
This is the custom validator which I have written to validate "MyValue"
public class MyCustomDoubleTypeAttribute : ValidationAttribute
{
public override bool IsValid(object value) //When I send "12,14" from client, the value gets assigned to this value is "1214". .Net thinks "," is a group separator and removes it.
{
string doubleValue = value.ToString();
try
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("no");
double convertedDouble = double.Parse(doubleValue);
string convertedString = convertedDouble.ToString(Thread.CurrentThread.CurrentCulture);
if (convertedString.Equals(doubleValue))
{
return true;
}
return false;
}
catch (FormatException formatException)
{
return false;
}
}
public override string FormatErrorMessage(string name)
{
return string.Format(CultureInfo.CurrentCulture,
ErrorMessageString,
name);
}
}
So this is my problem. What I want is to get the value as I enter in the client side to the input parameter of IsValid(Object value) method.
Thanks in advance.
You might be able to use a custom model binder. I know that you can use this on a regular MVC site, so I would assume that the same or similar code could be leveraged on Web API. There is a good example of using a custom binder for parsing double values at Phil Haack's site.
The culprit is probably Json.NET that's translating 12,14 into 1214 for you. You should look into writing a custom converter that is more careful about that.
You should be able to find instructions to write your own converter on the web. Here's an example:
How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
I have a problem on converting JSON string to object in Android. Here are the JSON structure and Java classes:
JSON:
{
"code":"SUCCEED",
"message":"",
"result":{
"ccahUserId": 111,
"ccahUserName":"your_name",
"userFirstName":"your_first_name",
"userLastName":"your_last_name",
//others
}
Java classes:
public class Result<T>{
public String code;
public String message;
public T result;
}
public class DeviceSetting
{
public long ccahUserId;
public String ccahUserName;
public String userFirstName;
public String userLastName;
//other members
}
Activity:
Gson gson = new Gson();
Result<DeviceSetting> setting = gson.fromJson(result, Result<DeviceSetting>.class);
When I deserialise the JSON string, code and message field were good but result field is null.
I am not familiar with Gson yet, so please help how to fix this problem?
Thanks in advance.
Likely the result field is null because it relies on the type parameter for Result.
From the GSON documentation for Gson.fromJson(JsonElement, Class<T>) (bolding is mine):
This method deserializes the Json read from the specified parse tree
into an object of the specified type. It is not suitable to use if the
specified class is a generic type since it will not have the generic
type information because of the Type Erasure feature of Java. Therefore, this method should not be used if the desired type is a generic type. Note that this method works fine if the any of the fields of the specified object are generics, just the object itself should not be a generic type. For the cases when the object is of generic type, invoke fromJson(JsonElement, Type).