Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpClient.PostJsonAsync deserialize json failed #11210

Closed
yan0lovesha opened this issue Jun 14, 2019 · 19 comments
Closed

HttpClient.PostJsonAsync deserialize json failed #11210

yan0lovesha opened this issue Jun 14, 2019 · 19 comments
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. External This is an issue in a component not contained in this repository. It is open for tracking purposes.

Comments

@yan0lovesha
Copy link

Describe the bug

When I use below code in razor page to post a http request. The deserialized object is not correct. There is no exception happened.

billInfo = await Http.PostJsonAsync<BillInfo>("api/TMobileBill", filePath);

I then changed to use Http.PostAsync to get the response string, then deserialize it by using Newtonsoft library. It works fine.

        var myContent = Newtonsoft.Json.JsonConvert.SerializeObject(filePath);
        var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
        var byteContent = new ByteArrayContent(buffer);
        byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json");
        var response = await Http.PostAsync("api/TMobileBill", byteContent);
        var responseStr = await response.Content.ReadAsStringAsync();
        billInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<BillInfo>(responseStr);
        StateHasChanged();

The response json string in my problem is:

{"period":"May 09, 2019 - Jun 08, 2019","dueDate":"Jul 01, 2019","totalAmount":326.03,"billInfoPerPerson":[{"name":"A B","phonesInfo":[{"phoneNumber":"(425) 111-1111","planHost":false,"usage":4.84,"regularCharge":30.0,"otherCharge":0.0,"totalCharge":0.0},{"phoneNumber":"(425) 222-2222","planHost":false,"usage":5.48,"regularCharge":30.0,"otherCharge":0.0,"totalCharge":0.0}],"totalAmount":60.0},{"name":"C D","phonesInfo":[{"phoneNumber":"(425) 333-3333","planHost":false,"usage":1.08,"regularCharge":20.0,"otherCharge":0.0,"totalCharge":0.0},{"phoneNumber":"(425) 444-4444","planHost":false,"usage":1.47,"regularCharge":20.0,"otherCharge":0.0,"totalCharge":0.0}],"totalAmount":40.0},{"name":"E F","phonesInfo":[{"phoneNumber":"(425) 555-5555","planHost":false,"usage":1.05,"regularCharge":20.0,"otherCharge":28.03,"totalCharge":0.0},{"phoneNumber":"(425) 666-6666","planHost":false,"usage":2.15,"regularCharge":30.0,"otherCharge":0.0,"totalCharge":0.0}],"totalAmount":78.03},{"name":"G H","phonesInfo":[{"phoneNumber":"(425) 777-7777","planHost":false,"usage":1.52,"regularCharge":20.0,"otherCharge":17.5,"totalCharge":0.0},{"phoneNumber":"(425) 888-8888","planHost":true,"usage":5.27,"regularCharge":30.0,"otherCharge":20.5,"totalCharge":0.0}],"totalAmount":88.0},{"name":"I J","phonesInfo":[{"phoneNumber":"(425) 999-9999","planHost":false,"usage":3.67,"regularCharge":30.0,"otherCharge":0.0,"totalCharge":0.0},{"phoneNumber":"(425) 000-0000","planHost":false,"usage":3.05,"regularCharge":30.0,"otherCharge":0.0,"totalCharge":0.0}],"totalAmount":60.0}],"warning":"There are $3 account service fee. Make sure it is expected. "}

The corresponding class is:

    public class PersonBillInfo
    {
        public string Name;
        public List<PhoneBillInfo> PhonesInfo;
        public float TotalAmount;
    }

    public class PhoneBillInfo
    {
        public string PhoneNumber;
        public bool PlanHost;
        public float Usage;
        public float RegularCharge;
        public float OtherCharge;
        public float TotalCharge;
    }

    public class BillInfo
    {
        public string Period;
        public string DueDate;
        public float TotalAmount;
        public IEnumerable<PersonBillInfo> BillInfoPerPerson;
        public string Warning;
    }

To Reproduce

Steps to reproduce the behavior:

  1. Using this version of ASP.NET Core 'SDK 3.0.100-preview6-012264'
  2. Implement a controller action to respond the json string as above.
  3. From razor page, use below code to call the controller action.
var billInfo = Http.PostJsonAsync<BillInfo>("api/TMobileBill", filePath);
  1. Evaluate the billInfo object. All the members of billInfo object are null.

Expected behavior

The members of billInfo object have values.

@ctrl-alt-d
Copy link

ctrl-alt-d commented Jun 14, 2019

Issue for Net Core Preview 6

( runs fine in preview 5 )

Same problem here with HttpClientJsonExtensions at Microsoft.AspNetCore.Components on dotnet core preview6:

Tis code is running fine on preview5 and fails silently on preview6:

r = await HttpClient.PostJsonAsync<DtoCapsule<SessionDto>>(url, parms);

@yan0lovesha 's workaround make the code running:

var myContent = Newtonsoft.Json.JsonConvert.SerializeObject(parms);
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json");
var response = await HttpClient.PostAsync(url, byteContent);
var responseStr = await response.Content.ReadAsStringAsync();
r = Newtonsoft.Json.JsonConvert.DeserializeObject<DtoCapsule<SessionDto>>(responseStr);

Additional info: the classes to deserialize in previous example ( "my code" ):

    public class SessionDto
    {
        public int Id { get; set; }
        public string JWT { get; set; }
    }


    public class DtoCapsule<T>
    {
        public T data;
        public IEnumerable<BrokenRule> brokenRules = new BrokenRules();
    }

    public class BrokenRule
    {
        public string Member { set; get; }
        public Severity Severity { set; get; }
        public string Message { set; get; }
    }

@analogrelay
Copy link
Contributor

Seems like it might be a difference with how System.Text.Json deserializes. cc @ahsonkhan

@yan0lovesha thanks for the sample JSON and classes, we can use that to see if we can repro the issue.

@analogrelay analogrelay added the area-blazor Includes: Blazor, Razor Components label Jun 14, 2019
@danroth27
Copy link
Member

@pranavkm @rynowak System.Text.Json issue

@danroth27 danroth27 added External This is an issue in a component not contained in this repository. It is open for tracking purposes. bug This issue describes a behavior which is not expected - a bug. labels Jun 14, 2019
@danroth27 danroth27 added this to the 3.0.0-preview7 milestone Jun 14, 2019
@yan0lovesha
Copy link
Author

@danroth27 Yes you're right. It was too late last night. I went a little bit deeper this morning. Below code statements on LinqPad6 can prove it:

var jsonString = "{\"period\":\"May 09, 2019 - Jun 08, 2019\",\"dueDate\":\"Jul 01, 2019\",\"totalAmount\":326.03}";

var jsonObjFromDotnetCore = System.Text.Json.Serialization.JsonSerializer.Parse<BillInfo>(jsonString);
var jsonObjFromNewtonsoft = Newtonsoft.Json.JsonConvert.DeserializeObject<BillInfo>(jsonString);

jsonObjFromDotnetCore.Dump();
jsonObjFromNewtonsoft.Dump();

}

public class BillInfo
{
	public string Period;
	public string DueDate;
	public float TotalAmount;

Seems that we need to wait for next preview release on this.

@danroth27
Copy link
Member

@yan0lovesha I believe the issue here is that System.Text.Json doesn't support doing serialization with public fields. Can you try using public properties instead?

@pranavkm
Copy link
Contributor

Here's what I tried with the settings PostAsJsonAsync currently uses and it works fine:

static void Main(string[] args)
{
    var jsonString = "{\"period\":\"May 09, 2019 - Jun 08, 2019\",\"dueDate\":\"Jul 01, 2019\",\"totalAmount\":326.03}";

    var jsonObjFromDotnetCore = JsonSerializer.Parse<BillInfo>(jsonString, new JsonSerializerOptions {PropertyNamingPolicy = JsonNamingPolicy.CamelCase});
    System.Console.WriteLine(jsonObjFromDotnetCore.Period);
}

public class BillInfo
{
    public string Period { get; set; }
    public string DueDate { get; set; }
    public float TotalAmount { get; set; }
}

I'd recommend filing an issue in https://github.com/dotnet/corefx/issues to consider adding support for public fields in a future release.

@ahsonkhan
Copy link
Member

ahsonkhan commented Jun 14, 2019

Yes, the reason for the missing info is that fields are not supported in the new System.Text.Json library (only public properties are supported).

We have an issue open for this already: https://github.com/dotnet/corefx/issues/36505

I have added the initial repro/post to that issue.

@yan0lovesha
Copy link
Author

As this blog mentioned. I tried to use services.AddMvc().AddJsonOptions(...) in Startup class. The default WeatherForecast api works fine. It can respond a correct json string. But for my own api controller, it just output an empty json object "{}". With the same api controller code, AddNewtonsoftJson works fine. This maybe another issue? Or it needs some special configuration?

@ctrl-alt-d
Copy link

ctrl-alt-d commented Jun 18, 2019

@yan0lovesha I believe the issue here is that System.Text.Json doesn't support doing serialization with public fields. Can you try using public properties instead?

Works for me changing public fields to public properties.

Doesn't run:

    public class DtoCapsule<T>
    {
        public T data ;
        public IEnumerable<BrokenRule> brokenRules = new BrokenRules();
    }

Runs fine:

    public class DtoCapsule<T>
    {
        public T data { set; get; }
        public IEnumerable<BrokenRule> brokenRules { set; get; }  = new BrokenRules();
    }

@petroemil
Copy link

I ran into this very same issue with my Client side Blazor web app, after upgrading to Preview 6. Serialization just doesn't work. But for me it doesn't work even if I make my properties public. I only get an empty object back.

@pranavkm
Copy link
Contributor

@yan0lovesha \ @petroemil could you please share a simple application that reproduces the error? It's difficult to investigate without knowing what it is that's being serialized.

@petroemil
Copy link

@pranavkm

My C# class looks like this:

public class BlogPostMetadata
{
    public bool IsDraft { get; set; }
    public DateTimeOffset PublishDate { get; set; }
    public string Title { get; set; }
    public string HeroImageFile { get; set; }
    public string SummaryMarkdownFile { get; set; }
    public string? MarkdownFile { get; set; }
    public string SocialSharingFile { get; set; }
}

A sample JSON content for it looks like this

{
  "Title": "Hello World",
  "PublishDate": "2019-05-17 16:19:34 -08:00",
  "HeroImageFile": "heroimage.jpg",
  "SummaryMarkdownFile": "summary.md",
  "MarkdownFile": "post.md",
  "SocialSharingFile": "socialshare.html"
}

My referenced ASP.NET libraries are:

<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="3.0.0-preview6.19307.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="3.0.0-preview6.19307.2" PrivateAssets="all" />

I'm getting this JSON from a file with the GetJsonAsync<T> extension method on the HttpClient but I also tried to manually use JsonSerializer.Parse<T> but of course with no luck.

I'm not getting any exception, just an empty object.

@pranavkm
Copy link
Contributor

@petroemil is the JSON produced from an MVC action? They're usually camel case which would explain why JsonSerializer.Parse does not work unless you also specify the camel case option.

@petroemil
Copy link

@pranavkm the JSON is coming from a file (wwwroot content) as shown in the example above. The JSON property names match the C# property names.

@ahsonkhan
Copy link
Member

Since the DateTimeOffset within the JSON is not ISO 8601 compliant (it contains spaces), the JsonSerializer throws an exception (see below, at least on preview 7). If the contents of PublishDate was changed to ""2019-05-17T16:19:34-08:00"", it works as expected (and the object is deserialized fully).

image

public class BlogPostMetadata
{
    public bool IsDraft { get; set; }
    public DateTimeOffset PublishDate { get; set; }
    public string Title { get; set; }
    public string HeroImageFile { get; set; }
    public string SummaryMarkdownFile { get; set; }
    public string MarkdownFile { get; set; }
    public string SocialSharingFile { get; set; }
}

[Fact]
public static void TestingStuff()
{
    string temp = @"{
        ""Title"": ""Hello World"",
        ""PublishDate"": ""2019-05-17 16:19:34 -08:00"", // spaces don't work
        ""HeroImageFile"": ""heroimage.jpg"",
        ""SummaryMarkdownFile"": ""summary.md"",
        ""MarkdownFile"": ""post.md"",
        ""SocialSharingFile"": ""socialshare.html""
    }";

    BlogPostMetadata foo = JsonSerializer.Parse<BlogPostMetadata>(temp);
    Assert.Equal("Hello World", foo.Title);
}
System.Text.Json.JsonException : The JSON value could not be converted to System.DateTimeOffset. Path: $.PublishDate | LineNumber: 2 | BytePositionInLine: 45.

cc @steveharter, @layomia

@petroemil
Copy link

@ahsonkhan wow, thank you very much.

So even in Client side Blazor the serializer only got changed to System.Text.Json in Preview 6 and before that it was still Newtonsoft? Because in Preview 5 this still worked fine.
Also I haven't seen the exception, only getting an empty object back for seemingly no reason.

@pranavkm
Copy link
Contributor

pranavkm commented Jun 20, 2019

@petroemil trying this out with preview7 I can see the error appear in the output:

info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 4.3179ms 200 application/json
warn: Microsoft.AspNetCore.Components.Browser.Rendering.RemoteRenderer[100]
      Unhandled exception rendering component: The JSON value could not be converted to System.DateTimeOffset. Path: $.PublishDate | LineNumber: 2 | BytePositionInLine: 45.
System.Text.Json.JsonException: The JSON value could not be converted to System.DateTimeOffset. Path: $.PublishDate | LineNumber: 2 | BytePositionInLine: 45.
   at System.Text.Json.ThrowHelper.ThowJsonException(String message, Utf8JsonReader& reader, String path)
   at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, Utf8JsonReader& reader, String path)
   at System.Text.Json.JsonPropertyInfoNotNullable`3.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.ParseCore(String json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Parse[TValue](String json, JsonSerializerOptions options)
   at Microsoft.AspNetCore.Components.HttpClientJsonExtensions.GetJsonAsync[T](HttpClient httpClient, String requestUri)

@yan0lovesha I'm closing this issue since it looks like using properties addressed the original issue you filed. Feel free to file a new work item if you're running in to further issues in this area.

@petroemil
Copy link

As a last note, if we are already here.

I'm still having issues getting the properly deserialized object.

Even if my class has { get; set; } properties, when I try to use .GetJsonAsync<T>() extension method on the HttpClient, I only get an empty object.
If I explicitly use .GetStringAsync() + JsonSerializer.Parse<T> together, only then it works.

I also noticed that { get; private set; } properties are not supported, neither are read-only ( { get; } ) properties with constructor initialization. So basically I can't deserialize an immutable object.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components bug This issue describes a behavior which is not expected - a bug. External This is an issue in a component not contained in this repository. It is open for tracking purposes.
Projects
None yet
Development

No branches or pull requests

8 participants