JSON, XML and Web Access
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Written against MATLAB R2026b. JSON, XML and web services meet in the same place in a program: configuration files, logs, and the answers of remote services are text in one of those formats, and MATLAB turns them into structures, tables and dictionaries. The functions arrived over many releases, and the release matters when you write code that other people run: webread came in R2014b, jsonencode and jsondecode and the matlab.net.http interface in R2016b, writestruct in R2020b, reading and writing JSON with readstruct and writestruct in R2023b, readdictionary in R2024b, secretID as a weboptions credential in R2025a, and reading and writing JSON with readtable and writetable in R2026a. The release numbers come from the "Introduced in" and "Since" notes of the R2026b reference pages.
GNU Octave 11.3.0 is not MATLAB here. Octave has jsonencode and jsondecode, and the examples that use only those, plus fopen, fprintf, fwrite and fileread, were run; their output is labelled "Octave 11.3.0 output". Octave has no readstruct, writestruct or readdictionary, no matlab.net.http, and its web functions are not run at all on this page. Everything that depends on those is labelled "not run here" and checked against the R2026b documentation. No example on this page touches the network. The addresses are placeholders (api.example.com), and keys are read from the environment or the MATLAB vault, never written in code (see Secrets and Protecting Code).
Which tool for which job
| Job | Function | Since |
|---|---|---|
JSON text in memory to MATLAB data, and back |
|
R2016b |
A JSON or XML file to a structure, and back |
|
|
A JSON file of key-value pairs to a dictionary |
|
R2024b |
A JSON or XML file to a table or timetable |
|
XML R2021a ( |
An XML file to a DOM tree you walk node by node |
|
long-standing; MAXP engine by default in R2026b |
A RESTful web service (GET, POST, save to a file) |
|
|
HTTP with full control of headers, methods, cookies, redirects and streaming |
|
R2016b |
Rules of thumb: use jsondecode when you already hold the text (a web response, a string in a script); use readstruct or readtable when the data is in a file and you want MATLAB to guess the types; use the web* functions for simple REST calls and the matlab.net.http classes only when those cannot express the request.
jsondecode: JSON text to MATLAB data
value = jsondecode(txt) parses JSON text given as a character vector or string scalar. The documentation states the rule that governs everything else: JSON supports fewer data types than MATLAB, so jsondecode converts each JSON type to the MATLAB type in this table.
| JSON | MATLAB |
|---|---|
|
|
|
empty double |
Boolean |
scalar |
Number |
scalar |
String |
character vector |
Object (an unordered set of name-value pairs) |
scalar structure; names are made valid with |
Array of mixed types |
cell array |
Array of booleans |
array of |
Array of numbers |
array of |
Array of strings |
cell array of character vectors |
Array of objects with the same field names |
structure array |
Array of objects with different field names |
cell array of scalar structures |
Three consequences follow, and they are the source of most surprises. First, the shape is not what the JSON looks like: a JSON array of numbers becomes a column vector (N-by-1), and an array of arrays becomes a matrix whose rows are the inner arrays. Second, numbers are always double and strings are always character vectors, even though string arrays are the modern text type (see Strings and Character Arrays); convert with string(...) afterwards. Third, the same JSON key can decode to different types depending on its neighbours: an array of objects with identical fields is a structure array you index with s(2).id, while one object with an extra field turns the whole result into a cell array you index with s{2}.id.
The following script was run in Octave 11.3.0. It decodes one object that contains most of the types in the table, and it shows the class and size of each field.
a = jsondecode('{"x":1,"ok":true,"name":"T1","v":[1,2,3],"flags":[true,false],"tags":["a","b"],"mix":[1,"two",true],"none":null,"nan":[1,null,3]}');
disp(class(a.x)); disp(class(a.ok)); disp(class(a.name)); disp(size(a.v)); disp(class(a.flags));
disp(class(a.tags)); disp(size(a.tags)); disp(class(a.mix)); disp(size(a.none)); disp(a.nan')
s1 = jsondecode('[{"id":"T1","x":0.1},{"id":"T2","x":0.4}]');
disp(class(s1)); disp(size(s1)); disp(s1(2).id)
s2 = jsondecode('[{"id":"T1","x":0.1},{"id":"T2","y":0.4}]');
disp(class(s2)); disp(size(s2))
m = jsondecode('[[1,2,3],[4,5,6]]');
disp(size(m)); disp(m)
Octave 11.3.0 output (MATLAB prints the same classes and sizes according to the table above):
double
logical
char
3 1
logical
cell
2 1
cell
0 0
1 NaN 3
struct
2 1
T2
cell
2 1
2 3
1 2 3
4 5 6
Read it against the table. v is 3-by-1, the null inside the numeric array became NaN, the lone null became an empty array (size 0-by-0), the mixed array became a cell array, the objects with equal fields became a 2-by-1 structure array, and the objects with different fields became a 2-by-1 cell array.
Field names. JSON allows any text as a key; MATLAB structure fields must be valid names. jsondecode passes every name through matlab.lang.makeValidName, so a key such as 1st reading is changed. In Octave 11.3.0 the keys 1st reading and a-b became the fields x1stReading and a_b; the MATLAB documentation says the same function is used. The documentation draws the consequence in its limitations: if you decode and then encode a value, MATLAB does not guarantee that the result is identical to the original text, and altered field names are one reason.
Source: MATLAB documentation: jsondecode
jsonencode: MATLAB data to JSON text
txt = jsonencode(data) returns a character vector in JSON format, and txt = jsonencode(data,Name=Value) accepts two name-value arguments:
| Name-value argument | Meaning |
|---|---|
|
|
|
|
The mapping of the documentation, restricted to the types you meet most often:
| MATLAB | JSON | Example result |
|---|---|---|
empty array |
empty array |
|
logical scalar, vector |
boolean, array of booleans |
|
character vector, string scalar |
string |
|
character array, string vector |
array of strings |
|
|
|
|
numeric scalar, vector, matrix |
number, array, nested array |
|
|
|
|
table |
array of objects, one per row |
|
cell scalar, cell vector |
array |
|
cell array (2-D) |
array flattened to one dimension |
|
structure scalar, object scalar |
object (an object’s public properties become name-value pairs) |
|
structure vector |
array of objects |
|
|
string, converted with the |
|
categorical |
string |
|
|
object |
|
enumeration |
string |
|
Not supported: complex numbers and sparse arrays, hidden properties, and recursive structures such as graphics objects that hold references to their parents and children. Round trips lose information: JSON does not distinguish double from int32, so an int32 encoded and decoded comes back as double, and a 1-by-N vector comes back as N-by-1. Text escapes follow the documentation tips: use newline to produce \n, pass strings through sprintf for tabs, and a double quote inside the text is escaped with a backslash.
These cases were run in Octave 11.3.0:
disp(jsonencode(struct('id','T1','x',0.1,'y',0.1)))
disp(jsonencode([1;2;3]))
disp(jsonencode(eye(2)))
disp(jsonencode([1 2 NaN 3 Inf]))
disp(jsonencode([1 NaN],'ConvertInfAndNaN',false))
disp(jsonencode(struct('a',{true,true,false})))
disp(jsonencode(int32(7)))
s = struct('width',800,'IDs',[116 943]);
disp(jsonencode(s,'PrettyPrint',true))
r = jsondecode(jsonencode(int32([1 2 3])));
disp(class(r)); disp(size(r))
Octave 11.3.0 output:
{"id":"T1","x":0.1,"y":0.1}
[1,2,3]
[[1,0],[0,1]]
[1,2,null,3,null]
[1,NaN]
[{"a":true},{"a":true},{"a":false}]
7
{
"width": 800,
"IDs": [
116,
943
]
}
double
3 1
A column vector encodes like a row vector ([1,2,3]), which is why shape is lost; the round trip of the int32 vector returns a double column. The MATLAB documentation writes the name-value pairs as PrettyPrint=true; Octave 11.3.0 accepted the comma form used above.
A scalar object as a one-element array. A structure encodes as an object, but some services expect an array even for one record. Put the structure in a cell array with {}, which the documentation recommends for this case. Octave 11.3.0 output of the following:
one = struct('sensor','T1','temperature',21.4);
disp(jsonencode(one))
disp(jsonencode({one}))
disp(jsonencode(struct('sensors',{{one}})))
disp(jsonencode(['say "hi"' char(10) 'ok']))
{"sensor":"T1","temperature":21.4}
[{"sensor":"T1","temperature":21.4}]
{"sensors":[{"sensor":"T1","temperature":21.4}]}
"say \"hi\"\nok"
Records or columns? A structure array (or a table) encodes as an array of records, [{"time":...,"sensor":"T1"},...], which is the shape most services use. A scalar structure whose fields are vectors encodes as columns, {"sensor":["T1","T2"],"temperature":[21.4,22.9]}, which is compact but gives the reader the work of zipping the columns.
Source: MATLAB documentation: jsonencode
The heatflow plate configuration, end to end
The heatflow scenario (The heatflow scenario) keeps its settings in data/plate.json. The script below writes a file, reads it back, changes two values, adds a third sensor and writes the result. It uses only the low-level file functions (see Low-Level File I/O and the File System) plus jsondecode and jsonencode, and was run in Octave 11.3.0 on a temporary folder. In MATLAB you can also replace the reading with readstruct, as the next section shows.
d = 'plate_json_tmp';
if ~exist(d,'dir'), mkdir(d); end
f = fullfile(d,'plate.json');
fid = fopen(f,'w');
fprintf(fid,'%s\n','{"width":0.5,"height":0.3,"nx":50,"ny":30,');
fprintf(fid,'%s\n',' "alpha":1.2e-5,"boundary":{"left":100,"right":0},');
fprintf(fid,'%s\n',' "sensors":[{"id":"T1","x":0.1,"y":0.1},{"id":"T2","x":0.4,"y":0.2}]}');
fclose(fid);
plate = jsondecode(fileread(f));
fprintf('%g x %g, %d x %d points\n', plate.width, plate.height, plate.nx, plate.ny);
plate.nx = 100;
plate.sensors(3).id = 'T3'; plate.sensors(3).x = 0.25; plate.sensors(3).y = 0.15;
txt = jsonencode(plate);
disp(txt)
fid = fopen(fullfile(d,'plate_out.json'),'w'); fwrite(fid,txt); fclose(fid);
back = jsondecode(fileread(fullfile(d,'plate_out.json')));
disp(numel(back.sensors)); disp(back.nx)
Octave 11.3.0 output (the number 1.2e-5 is written by Octave as 0.000012; MATLAB may format it differently):
0.5 x 0.3, 50 x 30 points
{"width":0.5,"height":0.3,"nx":100,"ny":30,"alpha":0.000012,"boundary":{"left":100,"right":0},"sensors":[{"id":"T1","x":0.1,"y":0.1},{"id":"T2","x":0.4,"y":0.2},{"id":"T3","x":0.25,"y":0.15}]}
3
100
Source: MATLAB documentation: jsondecode, MATLAB documentation: jsonencode, MATLAB documentation: fileread
The sensors array is a structure array in memory, so adding a third element is an assignment to plate.sensors(3), and jsonencode turns it back into an array of three objects. Note that jsondecode does not validate anything: a misspelt key (widht) simply becomes another field. Check the fields you need with isfield, or with the validation tools of Argument Validation, before the simulation uses them.
Invalid JSON. jsondecode stops with an error on text that is not valid JSON; the same script, run on {"a":1,} (a trailing comma), reached the catch block in Octave 11.3.0. The wording and identifier of the MATLAB error are not shown on the reference page, so catch it with a general try/catch and report ME.message (see Errors, Warnings and Exceptions); the message varies by release.
readstruct and writestruct
S = readstruct(filename) creates a structure array from a file that contains well-formed XML or JSON, and detects the data types of the contents. S = readstruct(filename,Name=Value) takes options such as FileType. The file type comes from the extension (.xml, or .json since R2023b), or from FileType="xml" or FileType="json" when the extension is something else, such as .info or .txt. The filename may be a path, an http:// or https:// URL, or a remote location (Amazon S3, Azure Blob Storage, HDFS); compressed .zip, .gz and .tar files are accepted since R2025a.
The documentation’s JSON example, music.json, has an object with nested objects and an array of objects. Its result shows how the mapping differs from jsondecode: the top-level result is always a structure, and arrays of objects whose elements have different keys become a uniform structure array with every key present in the type, where the keys missing from an element are filled with missing.
S = readstruct("music.json")
% S = struct with fields:
% Ensemble: [1×1 struct]
% Musicians: [1×5 struct]
ins = S.Ensemble.Instrumentation % 1×4 struct array with fields: Type Instrument Pianotype Drumkit Basstype
[ins1,ins2,ins3,ins4] = S.Ensemble.Instrumentation.Pianotype
% ins1 = <missing>, ins2 = "concert grand", ins3 = <missing>, ins4 = <missing>
Not run here (the example of the reference page; the display is copied from it). The difference from jsondecode is practical: with readstruct you can write S.Ensemble.Instrumentation(2).Pianotype and get a string or missing, whereas jsondecode returns a cell array of structures with different fields that you must test element by element. The price is that readstruct needs a file or URL, not text in memory, and returns text as string values, as the displays of the reference page show.
JSON parsing options (R2023b). ParsingMode="strict" makes the parser follow the JSON standard exactly, and AllowTrailingCommas=true tolerates a comma after the last element. The documentation’s example reads music.json, which has trailing commas, with both: S = readstruct("music.json",ParsingMode="strict",AllowTrailingCommas=true). Other options select part of the file: StructSelector takes an XPath expression for XML, and RegisteredNamespaces registers a prefix for a namespace.
XML mapping. Elements become fields, repeated sibling elements become a structure array, an attribute is a field of its element, and the text of an element that also has attributes or children is stored in a field named Text. The reference page shows that <Instrument type="wind">Trumpet</Instrument> becomes a structure with the fields type and Text. The suffix that marks an attribute can be set with AttributeSuffix.
writestruct
writestruct(S,filename) writes a structure to XML or JSON. The documented name-value arguments are FileType ("xml" or "json", JSON since R2023b), PrettyPrint (default true; since R2023b, four spaces of indentation), StructNodeName (the root element of an XML file, default "struct"), AttributeSuffix (default "Attribute": fields whose names end in that suffix are written as attributes, with the suffix dropped) and PreserveInfAndNaN (since R2023b; false writes Inf and NaN as null). An existing file is overwritten.
S = readstruct("music.xml");
writestruct(S,"band.xml","StructNodeName","JazzBand")
S = readstruct("music.xml","AttributeSuffix","_att");
writestruct(S,"band.xml","AttributeSuffix","_att")
Not run here. The first call writes a file whose root element is <JazzBand>; in the output of the reference page every text node of an element with attributes is wrapped in <Text>. The second call reads the attributes as fields with the suffix _att (role_att, type_att) and writes them back as real attributes without the suffix. Without the matching AttributeSuffix in the write, the same fields are written as child elements named type_att, which is a different document. Writing JSON with PreserveInfAndNaN=false replaced the NaN values of the example with null and wrote integers such as 5 as 5.0 in the output of the reference page.
Heatflow. A plate configuration kept in XML would read like this (not run here, the file name is an example):
plate = readstruct("data/plate.xml");
nx = plate.nx; % double
for k = 1:numel(plate.sensors.sensor)
id = plate.sensors.sensor(k).id; % string
end
JSON as tables, timetables and dictionaries
Tables (R2026a). readtable reads files with the extension .json since R2026a and, by default, creates one table variable for each object key it detects, named after the key. The documentation’s example reads only the street of each student from a file that holds an array of objects with nested address objects: detectImportOptions creates a JSONImportOptions object, and its VariableSelectors name-value argument takes a JSON Pointer (a path such as /Address/Street) to the keys that become variables.
opts = detectImportOptions("students.json",VariableSelectors="/Address/Street");
T = readtable("students.json",opts)
% T = 3×1 table
% Street
% ___________________
% "591 Spring Lane"
% "4641 Pearl Street"
% "30 Highland Road"
Not run here (the example of the readtable reference page). For the thermocouple log, the same mechanism applies to a JSON file with one object per reading, [{"Time":"...","SensorID":"S1","Temperature":21.4},...]. Writing is documented for writetable: the extension .json selects JSON, FileType, PrettyPrint and PreserveInfAndNaN apply, and writetable lists "Write JSON files" in R2026a.
Timetables (R2026a). readtimetable and writetimetable gained JSON in the same release: readtimetable recognises the .json extension (or FileType="json" for another extension) and reads the data as a timetable, and the version history of writetimetable lists "Write JSON files" in R2026a. As with other formats, readtimetable takes the row times from the first datetime or duration variable unless RowTimes names another, so the thermocouple log written as one object per reading becomes the timetable used on Timetables.
Dictionaries (R2024b). d = readdictionary(filename) reads a JSON file whose content is one object of keys and values and creates a dictionary (see Dictionaries and containers.Map). With the documentation’s citytemps.json ({"Boston": 55, "New York": 62, ...}) the result is a dictionary (string ⟼ double) with five entries. When the values have different types, as in msginfo.json, the dictionary uses the type cell for its values and each value is wrapped in a cell. A null becomes missing. The name-value arguments include FileType, DateLocale, ValueType (for example "string" to import all values as strings), DictionaryNodeName (start at the first node with that name) and DictionarySelector (a JSON Pointer to the starting element). writedictionary is the inverse; the supported-formats table lists both.
d = readdictionary("sensor_x.json") % {"T1": 0.1, "T2": 0.4}
% dictionary (string ⟼ double) with 2 entries
d("T2")
Not run here; the file is an example in the style of the documentation’s citytemps.json, and the display is what the reference page shows for that file.
XML when you need the tree: xmlread
DOMnode = xmlread(filename) parses an XML file and returns a Document Object Model node, a tree you walk with getElementsByTagName, getChildNodes and similar methods. It is the right tool when the structure of the document matters (mixed content, comments, namespaces), and the wrong one for plain data, where readstruct or readtable give you MATLAB types directly. The reference page includes a parsing function, parseXML, that converts the tree into a structure array with the fields Name, Attributes, Data and Children. The matching writer is xmlwrite. In new code, prefer readstruct, readtable and writestruct.
Which engine builds the tree. For most of its history xmlread returned a Java DOM (the Java API for XML Processing, JAXP), so it needed MATLAB’s Java virtual machine. In R2026b it uses the MATLAB API for XML Processing (MAXP, the matlab.io.xml.dom classes) by default, and XMLEngine="jaxp" restores the Java DOM. The two engines return objects with different methods, so older code that calls Java org.w3c.dom helpers may need rewriting, as the reference page warns. xmlwrite accepts MAXP nodes since R2026a; before that it took only a JAXP node. Anything that still produces a Java DOM, such as XMLEngine="jaxp", a node built with com.mathworks.xml.XMLUtils or webread with ContentType="xmldom", needs a Java runtime, which R2026b does not install with MATLAB: install one with the MATLAB Support for OpenJDK add-on and select it with jenv, and check usejava("jvm") first. readstruct and writestruct return and take MATLAB structures and need no Java DOM.
Reading from the web: webread
data = webread(url) reads content from a web service and converts it to a MATLAB type according to the content type of the response. Only http and https are supported. Query parameters are given as name-value pairs after the URL, data = webread(url,"firstName","John","lastName","Smith"), and the function builds the query string, encodes the special characters and appends it to the URL. A weboptions object adds the other request options, and the output type can be forced with its ContentType property:
ContentType |
Output |
|---|---|
|
Determined by the content type the service returns. |
|
Character vector for |
|
|
|
Scalar table for spreadsheet and CSV content. |
|
Numeric matrix, as |
|
|
|
Java DOM node for XML content (needs a Java runtime; see the |
|
|
The reference page notes that webread also supports HTTP POST (set RequestMethod to "post"), but it puts its queries in the URL, and for POST it only supports the application/x-www-form-urlencoded media type; use webwrite to send a body. webread does not examine the document to find its encoding: if the text is garbled, set CharacterEncoding in weboptions (for example "UTF-8").
The following request fetches the latest temperature readings from a heatflow data service. The address is a placeholder, HEATFLOW_API_KEY is read from the environment (see Secrets and Protecting Code), and nothing was sent while writing this page.
key = getenv("HEATFLOW_API_KEY"); % set by the vault, a .env file or the deployment
assert(strlength(key) > 0, "HEATFLOW_API_KEY is not set")
opts = weboptions(ContentType="json", Timeout=20, ...
HeaderFields=["Authorization" "Bearer " + key]);
url = "https://api.example.com/v1/readings";
data = webread(url, "sensor", "T1", "limit", 100, opts);
% data is already decoded (a structure array if the service sends an array of equal objects)
T = struct2table(data);
Not run here (placeholder address; no network access was used). The expression "Bearer " + key builds the header value; the two-column string array is the HeaderFields format of the documentation. If the service answers with something other than JSON the call fails or returns another type, so name the type you expect with ContentType and handle a failure:
try
data = webread(url, "sensor", "T1", opts);
catch ME
warning("heatflow:webFailed", "Reading failed: %s", ME.message);
data = [];
end
Not run here. Catch the failure and report ME.message; the identifiers and messages for HTTP status codes and timeouts are not listed on the webread reference page and vary by release, so do not match on them.
weboptions
options = weboptions(Name=Value) creates the object with the request settings. The properties of the R2026b reference page are:
| Property | Meaning (default) |
|---|---|
|
Encoding used to convert web content to characters ( |
|
Client identification string ( |
|
Seconds to wait for the initial response header after the request is sent ( |
|
Basic or digest authentication (the documentation marks them "no encryption"); can be a |
|
One extra name and value added to the HTTP request header, typically an API key. |
|
m-by-2 string array or cell array of header names and values; they add to or replace automatic fields (not |
|
Requested output type ( |
|
A function handle that reads the downloaded content, for example |
|
Content type that |
|
|
|
How array query values are encoded: |
|
Certificate file ( |
A default weboptions object is the common starting point; the Timeout of 5 seconds is short for slow services, and a longer value is the first change to make.
Credentials. The documentation shows that Username and Password may be secretID objects, so the values stay in the vault and the object displays only the identifier:
options = weboptions(Username=secretID("HeatflowService.User"), ...
Password=secretID("HeatflowService.Password"));
Not run here (the identifiers are labels; values are stored with setSecret). A Password given as a string is displayed as asterisks but stored as plain text in the object, so never save a weboptions object that holds one in a MAT-file, a log or a project.
Sending data: webwrite and websave
response = webwrite(url,PostName1,PostValue1,...) form-encodes the pairs in the body of an HTTP POST request. response = webwrite(url,data) posts data and weboptions controls the media type through MediaType. The documented rules are:
-
With pairs, MATLAB uses
application/x-www-form-urlencoded. -
A scalar string or character vector
datawith the default"auto"media type is treated as a form-encoded string and sent as it is. -
Any other
data(a structure, a numeric array, a table) is converted withjsonencodeand sent asapplication/json. -
With
MediaType="json"or"javascript"and a character vector, the text is sent as it is, so it is not encoded twice; every other type, including a scalar string, goes throughjsonencode.
To post one record as a JSON array, wrap it in braces, webwrite(url,{record},options), for the reason given in the jsonencode section above. The PUT, PATCH and DELETE methods are chosen with the RequestMethod property of weboptions.
reading = struct("sensor","T1", "temperature",21.4, "time",string(datetime("now","Format","yyyy-MM-dd'T'HH:mm:ss")));
opts = weboptions(MediaType="application/json", Timeout=20, ...
KeyName="X-Api-Key", KeyValue=getenv("HEATFLOW_API_KEY"));
resp = webwrite("https://api.example.com/v1/readings", {reading}, opts);
Not run here (placeholder address; no network access). The reading is wrapped in braces so that the service receives an array of one object. KeyName and KeyValue add the key to the request header; the key comes from the environment, not from the source file.
outfilename = websave(filename,url) saves the response to a file and returns its full path. It saves the content as it is and ignores ContentType and ContentReader, so it is the choice for images, archives and large files, and for keeping the raw JSON before decoding it. The documentation’s pattern for JSON is to save, then read with fileread and jsondecode:
f = websave("readings.json", "https://api.example.com/v1/readings", "sensor", "T1");
data = jsondecode(fileread(f));
Not run here. Saving first is also a good habit for reproducibility: the file is the record of what the service answered at that moment.
When the web functions are not enough: matlab.net.http
The documentation says directly that some interactions with a web service are more complex than the webread, webwrite and websave functions can express. The HTTP interface provides classes for messages, header fields and other entities defined in the IETF standards, and its sections cover sending and receiving messages with redirection and digest authentication, data type conversion, cookies, progress monitors, streaming consumers and providers, multipart form messages and manual redirects. Only HTTP 1.1 is supported by this interface and by the web* functions.
The building blocks of a request are matlab.net.http.RequestMessage (its properties are RequestLine, Method, Header and Body, and send transmits it), matlab.net.http.MessageBody, the header field classes of the matlab.net.http.field namespace such as ContentTypeField and AcceptField, matlab.net.http.MediaType, matlab.net.http.RequestMethod and matlab.net.URI. The reference page of RequestMessage builds a message without sending it, and the pattern is:
body = matlab.net.http.MessageBody('Data to send');
ctype = matlab.net.http.field.ContentTypeField('text/plain');
accept = matlab.net.http.field.AcceptField(matlab.net.http.MediaType('application/json'));
req = matlab.net.http.RequestMessage(matlab.net.http.RequestMethod.PUT, [accept ctype], body);
show(req) % prints the message; nothing is sent
% resp = send(req, matlab.net.URI("https://api.example.com/v1/readings")); % would send it
Not run here, and send is commented out on purpose: no request was made. The first four lines follow the documentation’s example; the address in the last line is a placeholder. Reach for this interface for custom headers that weboptions cannot express, for streaming a large response, for uploading multipart forms or for reading status codes and response headers; for everything else, webread is shorter.
Common errors and pitfalls
| Symptom | Cause | Fix |
|---|---|---|
The decoded array is a column when the JSON was a row |
|
Transpose with |
Indexing with |
Arrays of objects are structure arrays only when all elements have the same fields; otherwise a cell array |
Check with |
Field names changed |
Keys that are not valid MATLAB names are altered by |
Read the keys from |
|
JSON has one number type |
Convert explicitly after decoding. |
Dates are text |
JSON has no date type |
Convert with |
|
|
Decide whether the reader accepts |
A request times out although the service answers |
The default |
Raise |
Garbled characters in a web response |
The document encoding differs from what |
Set |
A password appears in a log or a saved file |
A |
Use |
|
A character vector that is already JSON was passed without a JSON |
Set |
Where older books differ
Older books fetch web pages with urlread. It still exists, but its reference page marks it as not recommended: for http and https use webread or webwrite. Before R2016b there was no JSON support in base MATLAB, and books of that period describe File Exchange parsers or hand-written regexp loops; jsondecode and jsonencode replace them, with a fixed type mapping. XML in older books means xmlread and a recursive walk of the DOM, or File Exchange helpers; readstruct (XML R2020b, JSON R2023b), readtable for XML (R2021a) and for JSON (R2026a) and readdictionary (R2024b) are newer than any such book. The web* functions also changed: R2025a allows secretID objects as weboptions credentials, so tutorials that put Username and Password in plain text no longer describe best practice. Older code that calls webread with Name, Value pairs in weboptions still works; since R2021a the Name=Value form is preferred.