JSON is language-neutral, but every language maps it onto its own types differently, and statically typed languages prefer classes and structs so the compiler can check field access. In this lesson you will serialize and deserialize the same user document in Java, C#, Go and PHP and learn the conventions and traps of each.
Java has no JSON support in the standard library; Jackson is the de facto choice and the default in Spring Boot. ObjectMapper maps JSON onto classes and records:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public record User(int id, String name, @JsonProperty("email_address") String email, boolean active) {}
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class); // text -> object
String pretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user); // object -> text@JsonProperty renames a field when the JSON key does not match the Java name. By default Jackson throws UnrecognizedPropertyException on keys the class does not declare; disable that with mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) when an API may add fields. Register JavaTimeModule to serialize java.time dates. Gson is the common alternative with the same shape.
.NET ships System.Text.Json in the framework. JsonSerializerDefaults.Web selects the settings you want for APIs: camelCase names and case-insensitive matching.
using System.Text.Json;
using System.Text.Json.Serialization;
public record User(int Id, string Name, [property: JsonPropertyName("email_address")] string Email, bool Active);
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
User user = JsonSerializer.Deserialize<User>(json, options)!;
string output = JsonSerializer.Serialize(user, options);Deserialize<T> returns null for the literal null, hence the !. Unknown properties are ignored. Older codebases use Newtonsoft.Json; JsonNode.Parse(json) gives dictionary-style access to documents without a class.
Go's standard library handles JSON through json.Marshal and json.Unmarshal, with struct tags controlling the mapping (see the complete program at the top of the lesson):
type User struct {
ID int `json:"id"`
Email string `json:"email,omitempty"` // omitted from output when empty
secret string // unexported: never serialized
}
err := json.Unmarshal([]byte(data), &u)
out, err := json.MarshalIndent(u, "", " ")Only exported (capitalized) fields participate, and without a tag the key is the field name exactly, so ID would produce "ID". Unknown keys are ignored unless you call DisallowUnknownFields() on a json.Decoder, the type used for reading streams such as r.Body. Decoding into map[string]any turns every number into float64; use typed structs or json.Number to keep large integers exact.
PHP has JSON built in. json_decode returns stdClass objects by default; pass true as the second argument for associative arrays, which most code prefers:
$user = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
echo $user['name'];
$out = json_encode($user, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);Without JSON_THROW_ON_ERROR, json_decode returns null on invalid input and you must check json_last_error_msg(), a classic silent failure. Two PHP-specific traps: an empty array encodes as [], so use new stdClass() or JSON_FORCE_OBJECT when the consumer expects {}, and integers beyond 64 bits become floats unless you pass JSON_BIGINT_AS_STRING. PHP 8.3 added json_validate() for syntax checks.
| Language | Library | Text to object | Object to text | Rename a field |
|----------|---------|----------------|----------------|----------------|
| Java | Jackson | mapper.readValue(json, T.class) | mapper.writeValueAsString(obj) | @JsonProperty |
| C# | System.Text.Json | JsonSerializer.Deserialize<T>(json) | JsonSerializer.Serialize(obj) | [JsonPropertyName] |
| Go | encoding/json | json.Unmarshal(bytes, &v) | json.Marshal(v) | struct tag |
| PHP | built in | json_decode($json, true) | json_encode($value) | array keys |
Two themes recur: every library has a rename mechanism because JSON keys rarely follow the language's naming rules, and none has a native date type, so dates travel as ISO 8601 strings.
In Go, why would a struct field named `secret` never appear in JSON output?
JSON_THROW_ON_ERROR to avoid silent null results.camelCase or snake_case) and map them explicitly in each language.Next lesson: JSON Configuration Files: package.json, tsconfig.json and JSONC — read and write the JSON files that configure real projects, and understand comments, $schema and their limits.