{
    "componentChunkName": "component---src-templates-blog-tsx",
    "path": "/blog/dalsoft-restclient-5-system-text-json-and-near-native-performance/",
    "result": {"data":{"mdx":{"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"DalSoft.RestClient 5.0 - System.Text.Json and Near-Native Performance\",\n  \"date\": \"2026-08-08T00:00:00.000Z\",\n  \"image\": \"./image.jpg\",\n  \"banner\": \"./banner.jpg\",\n  \"description\": \"RestClient 5.0 moves to System.Text.Json by default and gets a big performance pass - typed requests now allocate the same as using HttpClient and System.Text.Json by hand.\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient\"\n  }, \"DalSoft.RestClient\"), \" 5.0 is the biggest release of our C# Rest Client in years, and it has two headlines: serialization is now powered by \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview\"\n  }, \"System.Text.Json\"), \", and a performance pass that gets typed requests to near-native HttpClient numbers.\"), mdx(\"p\", null, \"It's a breaking release - but for most code the migration is one line, and we'll show you exactly what changed and why.\"), mdx(\"h3\", null, \"System.Text.Json by default\"), mdx(\"p\", null, \"Since the very first release RestClient has used the brilliant \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.newtonsoft.com/json\"\n  }, \"Json.NET\"), \" - it served us well for a decade. But System.Text.Json is where the .NET ecosystem lives now, and from 5.0 it's our default serializer.\"), mdx(\"p\", null, \"We deliberately made it behave the way a System.Text.Json user would expect - stock behaviour, not a Json.NET emulation:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"[JsonPropertyName]\"), \" attributes are honoured\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Property matching is case sensitive\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Failed typed casts throw \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"System.Text.Json.JsonException\"))), mdx(\"p\", null, \"The one place we deviate from stock is when reading - real world systems are less than perfect and send us less than perfect JSON, so trailing commas and comments are accepted by default. A reasonable compromise over strict parsing.\"), mdx(\"p\", null, \"Want to customise serialization? Pass your own \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"JsonSerializerOptions\"), \":\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"var config = new Config()\\n    .SetJsonSerializerOptions(new JsonSerializerOptions(JsonSerializerDefaults.Web));\\n\\ndynamic client = new RestClient(\\\"https://api.github.com\\\", config);\"), \"\\n        \"), mdx(\"h3\", null, \"Json.NET isn't going anywhere\"), mdx(\"p\", null, \"If your models rely on the legacy Json.NET behaviour - \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"[JsonProperty]\"), \" attributes, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"JsonSerializerSettings\"), \", case insensitive matching, lenient date parsing - opt back in with one line and you get the 4.x behaviour back exactly:\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"var config = new Config().UseNewtonsoftJson(); // optionally takes your JsonSerializerSettings\"), \"\\n        \"), mdx(\"p\", null, \"That's the whole migration for most codebases. We didn't want any implicit magic here - if you want Json.NET you ask for it, otherwise you get System.Text.Json.\"), mdx(\"h3\", null, \"The breaking changes\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Config.JsonSerializerSettings\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"SetJsonSerializerSettings\"), \" have been removed - use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"UseNewtonsoftJson(jsonSerializerSettings)\"), \" instead.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"[JsonProperty]\"), \" attributes are no longer honoured by default - use \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"[JsonPropertyName]\"), \" or opt in to Json.NET.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Property matching is case sensitive by default - watch out for models that only worked because Json.NET matched case insensitively. This one bit us in our own test suite:\")), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"csharp\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"public class User\\n{\\n    // jsonplaceholder returns \\\"username\\\" - Json.NET happily matched this case insensitively,\\n    // System.Text.Json needs the name mapped\\n    [JsonPropertyName(\\\"username\\\")]\\n    public string Username { get; set; }\\n}\"), \"\\n        \"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Failed typed casts now throw \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"System.Text.Json.JsonException\"), \" rather than the Json.NET exception types.\")), mdx(\"p\", null, \"Everything else - the dynamic API, the pipeline, the fluent chaining - works exactly as it always has.\"), mdx(\"h3\", null, \"The performance pass\"), mdx(\"p\", null, \"Inspired by \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/nikouu/HttpClientBenchmarking\"\n  }, \"nikouu's excellent HttpClientBenchmarking\"), \" repo (well worth a read if you care about HttpClient performance), we added a \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://benchmarkdotnet.org/\"\n  }, \"BenchmarkDotNet\"), \" project to the repo and went hunting. What we found and fixed:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"UTF-8 all the way through\"), \" - request bodies are serialized straight to UTF-8 bytes (no intermediate UTF-16 string), and response bodies are read as UTF-8 bytes with the string only decoded if you ask for it.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"The JSON DOM is now lazy\"), \" - casting a response to your model deserializes directly from the UTF-8 body and never builds a JSON DOM at all. The DOM is only built the first time you use dynamic access.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Arrays are wrapped lazily\"), \" - \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"result.items[0]\"), \" on a 10,000 element response used to eagerly wrap all 10,000 elements, now you only pay for what you touch.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"A pile of allocation trims\"), \" - cached statics, no more LINQ closures in the request hot path, reflection caching, and URL building that no longer re-splits the same string three times per request.\")), mdx(\"p\", null, \"Before and after, 10,000 item JSON payload, in process (no network), .NET 8:\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Scenario\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"4.x\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"5.0\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Typed response \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"List<User>\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"22.1 ms / 9.87 MB\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"13.5 ms / 5.10 MB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Dynamic access \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"result[0].id\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"8.86 ms / 6.59 MB\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"5.28 ms / 5.46 MB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"POST \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"List<User>\"), \" body\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"5.83 ms / 3.40 MB\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"3.69 ms / 1.13 MB\")))), mdx(\"p\", null, \"Measured against using HttpClient and System.Text.Json by hand, POST request bodies and typed responses now allocate about the same (1.0x) - down from 3.0x and 3.5x respectively in 4.x.\"), mdx(\"h3\", null, \"How does it compare to other REST clients?\"), mdx(\"p\", null, \"We also benchmarked a real GET request to the GitHub API deserialized to a typed model, against native HttpClient and the other popular .NET REST clients:\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Client\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Median\"), mdx(\"th\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Allocated\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"HttpClient + GetFromJsonAsync\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"51.01 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"11.38 KB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, mdx(\"strong\", {\n    parentName: \"td\"\n  }, \"DalSoft.RestClient 5.0\")), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"41.38 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"23.24 KB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"RestSharp 114.0\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"45.45 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"135.74 KB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Flurl.Http 4.0.2\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"19.64 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"19.79 KB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"Refit 15.0\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"39.55 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"17.80 KB\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"RestClient.Net 7.2.1\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"51.07 ms\"), mdx(\"td\", {\n    parentName: \"tr\",\n    \"align\": null\n  }, \"15.27 KB\")))), mdx(\"p\", null, \"An honest word about that table - time over a real network is dominated by latency, so every client lands within noise of native HttpClient. Don't read the time column as a ranking (including where a client appears faster than native - that's statistical noise). The allocations column is what shows the overhead each library adds per request, and that's where the story is: DalSoft.RestClient stays close to native and the lightweight clients while giving you a full dynamic API, and allocates about 6x less than RestSharp.\"), mdx(\"p\", null, \"The benchmarks live in the repo - \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dotnet run -c Release\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DalSoft.RestClient.Benchmarks\"), \" project to reproduce them.\"), mdx(\"h3\", null, \"net8.0 target\"), mdx(\"p\", null, \"RestClient now multi-targets \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"netstandard2.0\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"net8.0\"), \". The netstandard2.0 build keeps supporting everything it always has, while the net8.0 build uses the in-box System.Text.Json and drops the legacy package dependencies entirely - if you're on a modern runtime your dependency graph just got smaller.\"), mdx(\"h3\", null, \"DalSoft.RestClient.Testing 5.0\"), mdx(\"p\", null, mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient.Testing\"\n  }, \"DalSoft.RestClient.Testing\"), \" - our package for testing REST APIs with the ASP.NET Core in-memory TestServer and WebApplicationFactory - has a matching 5.0 release: upgraded to RestClient 5.0 and retargeted to net8.0. The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Verify\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"CreateRestClient\"), \" APIs are unchanged.\"), mdx(\"h3\", null, \"Get it\"), mdx(\"deckgo-highlight-code\", {\n    \"language\": \"bash\",\n    \"terminal\": \"carbon\",\n    \"theme\": \"vscode\",\n    \"line-numbers\": \"true\"\n  }, \"\\n          \", mdx(\"code\", {\n    parentName: \"deckgo-highlight-code\",\n    \"slot\": \"code\"\n  }, \"> dotnet add package DalSoft.RestClient\"), \"\\n        \"), mdx(\"p\", null, \"The full breaking change list and migration notes are in the \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient\"\n  }, \"readme\"), \". If you hit anything we missed, \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/DalSoft/DalSoft.RestClient/issues\"\n  }, \"raise an issue\"), \" - and if RestClient is useful to you or your company, please consider \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/sponsors/dalsoft\"\n  }, \"becoming a sponsor\"), \" \\u2764\\uFE0F\"));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"DalSoft.RestClient 5.0 - System.Text.Json and Near-Native Performance","date":"08 August 2026","description":"RestClient 5.0 moves to System.Text.Json by default and gets a big performance pass - typed requests now allocate the same as using HttpClient and System.Text.Json by hand.","banner":{"publicURL":"/static/5c142daba434e5eec75f555afecbae32/banner.jpg","childImageSharp":{"fluid":{"srcSet":"/static/5c142daba434e5eec75f555afecbae32/d34c9/banner.jpg 480w,\n/static/5c142daba434e5eec75f555afecbae32/f7c79/banner.jpg 960w,\n/static/5c142daba434e5eec75f555afecbae32/85bc9/banner.jpg 1920w,\n/static/5c142daba434e5eec75f555afecbae32/a91e7/banner.jpg 2400w","base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwEC/9oADAMBAAIQAxAAAAHHCnBm/wD/xAAXEAADAQAAAAAAAAAAAAAAAAAAARFB/9oACAEBAAEFAsQ5f//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABYQAAMAAAAAAAAAAAAAAAAAAAAQMf/aAAgBAQAGPwIq/8QAGBABAAMBAAAAAAAAAAAAAAAAEQABIVH/2gAIAQEAAT8hzlYKuN4k/9oADAMBAAIAAwAAABBwL//EABcRAQEBAQAAAAAAAAAAAAAAAAEAIUH/2gAIAQMBAT8Q4wZf/8QAFhEBAQEAAAAAAAAAAAAAAAAAABFB/9oACAECAQE/ENV//8QAGhAAAgIDAAAAAAAAAAAAAAAAASEAMRFxsf/aAAgBAQABPxBrqbguG0guwwAZZXP/2Q==","aspectRatio":2.4,"src":"/static/5c142daba434e5eec75f555afecbae32/85bc9/banner.jpg","sizes":"(max-width: 1920px) 100vw, 1920px"},"id":"bd3e4355-a77c-51a6-b4fb-41883aa30dde"}}}}},"pageContext":{"slug":"/blog/dalsoft-restclient-5-system-text-json-and-near-native-performance/"}},
    "staticQueryHashes": ["1139857438","1946588481","2083862410","2213455283","2418326273","2840686334","3067102388"]}