Serialize and Deserialize Data
How to serialize and deserialize data in JSON, XML, and YAML across Python, Java, and JavaScript.
Overview
Serialization turns an in-memory object into a storable, transmittable format; deserialization reverses the process and rebuilds the object from bytes or text. You’ll hit both everywhere: APIs, caches, message queues, config files, session persistence. Working snippets for JSON, XML and YAML in Python, JavaScript and Java come right after this. If you need flat key-value pairs instead of a serialized byte stream, see Flatten and Unflatten Nested Objects.
When to Use
Reach for serialization when you need to:
- Ship payloads across HTTP APIs or a message broker
- Persist app state on disk or in a cache layer
- Move a config between formats (JSON ↔ YAML ↔ XML)
- Pass typed messages between services that share no codebase
Solution
Python
import json
# Serialize (object -> JSON string)
data = {'name': 'Alice', 'age': 30, 'active': True}
json_str = json.dumps(data, indent=2)
print(json_str)
# Deserialize (JSON string -> object)
parsed = json.loads(json_str)
print(parsed['name'])
# YAML serialization with PyYAML
import yaml
yaml_str = yaml.safe_dump(data, default_flow_style=False)
parsed_yaml = yaml.safe_load(yaml_str)
JavaScript
// JSON is native to JavaScript
const data = { name: 'Alice', age: 30, active: true };
// Serialize
const jsonStr = JSON.stringify(data, null, 2);
console.log(jsonStr);
// Deserialize
const parsed = JSON.parse(jsonStr);
console.log(parsed.name);
// YAML serialization with js-yaml
// npm install js-yaml
import yaml from 'js-yaml';
const yamlStr = yaml.dump(data);
const parsedYaml = yaml.load(yamlStr);
Java
// Jackson is the standard for JSON in Java
// Maven: com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerializationDemo {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User("Alice", 30, true);
// Serialize
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(json);
// Deserialize
User parsed = mapper.readValue(json, User.class);
System.out.println(parsed.getName());
}
}
class User {
private String name;
private int age;
private boolean active;
public User() {}
public User(String name, int age, boolean active) {
this.name = name; this.age = age; this.active = active;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}
Explanation
JSON is the dominant interchange format due to its simplicity and native support in JavaScript and modern languages. It maps cleanly to dictionaries/objects, arrays, strings, numbers, booleans, and null. XML remains relevant in enterprise SOAP services, configuration files (Spring, Android), and document-centric workflows. YAML is preferred for human-edited configs because it supports comments and complex nesting with minimal syntax.
Under the hood, Jackson reflects on your POJO — it binds JSON fields through getters/setters or, if you let it, public fields. Python json works with any JSON-serializable type (dicts, lists, primitives). JavaScript JSON.stringify handles cycles poorly (throws TypeError) unless a replacer is provided.
Variants
| Technology | Format | Library | Approach | Notes |
|---|---|---|---|---|
| Python | JSON | json (stdlib) | dumps() / loads() | Handles basic types, custom encoder support |
| Python | YAML | PyYAML | safe_dump() / safe_load() | Supports custom tags and anchors |
| Python | XML | xml.etree.ElementTree | tostring() / fromstring() | Standard library, no schema validation |
| JavaScript | JSON | Native | JSON.stringify() / JSON.parse() | Zero dependencies, supports reviver/replacer |
| JavaScript | YAML | js-yaml | dump() / load() | Fast, safe by default |
| Java | JSON | Jackson | writeValueAsString() / readValue() | POJO binding, streaming, tree model |
| Java | XML | JAXB / Jackson XML | Annotation-driven | JAXB is deprecated; prefer Jackson XML |
Text formats (JSON, XML, YAML) are self-describing and debuggable; binary formats (MessagePack, Protocol Buffers, Avro) are smaller and faster to parse but need shared schemas and tooling to inspect. Pick schema-first formats when services owned by different teams exchange data — the contract lives outside the code — and schemaless JSON when flexibility matters more than payload size.
What Works
- Use
safe_loadfor YAML in untrusted contexts to prevent arbitrary code execution - Validate against a schema after deserialization to catch structural errors before business logic — see Validate JSON Schema
- Handle circular references explicitly in
JSON.stringifywith a replacer or a library likeflatted - Version your serialized data by adding a
schema_versionfield for backward-compatible evolution - Prefer JSON for APIs and YAML for configs; avoid XML unless integrating with legacy systems
Common Mistakes
- Not handling
undefinedin JavaScript:JSON.stringifysilently dropsundefinedvalues, functions, andSymbol-keyed properties. A field present in memory can vanish from the wire payload without any error. - Forgetting default constructors in Java: Jackson requires a no-arg constructor for deserialization
- Using
floatfor monetary values: serialization can introduce precision errors; useDecimal/BigDecimal - Not setting content-type headers: APIs should send
application/json, nottext/plain - Ignoring encoding issues: always specify UTF-8 when reading and writing serialized text files
- Loading YAML with an unsafe loader:
yaml.load()in PyYAML can instantiate arbitrary objects; usesafe_loadon anything you don’t fully trust - Assuming JSON preserves key order and exact numbers: objects are unordered, and JavaScript loses integer precision beyond 2^53 — use strings or
BigIntfor large IDs
When Not to Use This Approach
- Binary payloads over internal links: if payload size is the bottleneck between your own services, a binary format like MessagePack or Protobuf beats text formats.
- Strongly-typed contracts across teams: when producers and consumers evolve independently, schemaless JSON drifts silently; adopt a schema evolution policy and contract tests instead.
- Streaming multi-gigabyte payloads: loading the whole document into memory fails at scale; switch to streaming parsers (Jackson
JsonParser, Pythonijson) or a columnar format. - Persisting sensitive objects directly: serializing an entity dumps the whole object graph, including fields you may not want stored; serialize dedicated DTOs instead.
Security Considerations
- Native object formats on untrusted input are RCE:
pickle.loads(), Java native serialization and PHPunserialize()run whatever the byte stream tells them to. Anything crossing your trust boundary goes through a data-only format — JSON, not pickle. - Prototype pollution via JSON merge: merging user-supplied JSON containing
__proto__orconstructorkeys can pollute JavaScript object prototypes. Strip dangerous keys or build the result on a null-prototype object. - Unsafe YAML loaders:
yaml.load()(PyYAML) and equivalent unsafe loaders construct arbitrary objects from the document, so keepsafe_load/SafeLoaderon anything that crosses your trust boundary. - XXE in XML parsers: a crafted external-entity declaration lets the parser read local files or hit internal endpoints. Turn off DTDs and external entities —
FEATURE_SECURE_PROCESSINGunder Java,defusedxmlunder Python. - Duplicate-key ambiguity: feed
{"role": "user", "role": "admin"}to two different parsers and you can get two different results — last value wins for most, first for a few. That gap is exploitable: a WAF reading the raw text may see one thing while your parser sees another. - Depth and size limits: a document nested 10,000 levels deep, or a string spanning hundreds of MB, will blow the stack or exhaust memory. Cap both at the boundary.
Performance Considerations
- stdlib vs optimized parsers: Python’s
jsoncovers most workloads;orjsonruns several times faster on high-throughput services and hands you bytes back. In JavaScript,JSON.parse/JSON.stringifyalready run on V8’s fast path. - Pretty-printing costs size:
indent=2inflates payloads roughly 20–40%. Use it for configs humans read, not for wire formats. - Binding vs streaming: Jackson’s
ObjectMapperallocates the full object graph; the streamingJsonParserAPI reads very large documents with nearly constant memory. - Binary formats when size matters: MessagePack and Protobuf payloads are typically several times smaller than equivalent JSON and parse faster — the trade-off is losing human readability and needing shared schemas.
- Cache serialized bytes: on hot paths serving immutable payloads, serialize once and keep the bytes — re-serializing the same object per request is wasted CPU.
Testing Strategy
- Round-trip tests: serialize, then deserialize, and assert structural equality. This catches silent type coercion — dates become strings, tuples become lists,
Decimalbecomesfloat. - Edge-case corpus:
{},null, deeply nested arrays, Unicode strings (emoji, RTL), and integers past 2^53 — JavaScript silently rounds those. - Type coverage: walk your schema and verify every field type survives the round-trip.
datetime,Decimal,bytes, and enums all need explicit encoders in Python’sjsonor custom serializers in Jackson. - Adversarial input: deeply nested JSON (thousands of levels), very large strings, tens of thousands of keys, and duplicate keys to probe parser limits.
- Contract tests across versions: one fixture per released schema version, all of them deserialized in CI. Breaking changes surface before deploy instead of in production.
Error Handling and Recovery
- Malformed input: catch parse errors at the boundary and return a structured error with the offending location (
json.JSONDecodeErrorin Python,SyntaxErrorin JS,JsonParseExceptionin Jackson) — never a bare 500. - Encoding mismatches: decode explicitly as UTF-8 and surface
UnicodeDecodeErrorearly. A file saved as UTF-16 or carrying a BOM fails in confusing ways when read as plain UTF-8. - Schema/version mismatch: a payload with no
schema_version, or one newer than you support, deserves a clear error and a migration path — not silently dropped fields. - Partial deserialization: a payload can be valid JSON and still miss required fields. Validate after parsing, not instead of it.
Troubleshooting
Object of type X is not JSON serializable(Python): the type has no default encoder — pass adefault=callable or subclassJSONEncoderfordatetime,Decimal,set, andbytes.TypeError: Converting circular structure to JSON(JavaScript): the object graph has cycles — use a replacer that skips back-references or a library likeflatted.- Jackson fails on unknown fields: either add the fields to the POJO or configure
FAIL_ON_UNKNOWN_PROPERTIEStofalsefor forward compatibility. - Garbled accents or
Ãcharacters in output: the file was written in one encoding and read in another. Specify UTF-8 on both ends and handle BOMs explicitly. - Dates come back as strings: JSON has no date type. Agree on ISO-8601 strings and parse them explicitly, or each side ends up with a different type.
Further Reading
Frequently Asked Questions
Which serialization format should I choose for microservices?
Inside your own fleet, Protobuf or MessagePack wins on payload size and typed contracts. For public APIs and anything humans read, JSON stays the right call — self-describing and understood everywhere.
How do I handle custom object serialization in Python?
Two options: subclass JSONEncoder, or hand json.dumps() a default= callable that returns a serializable dict for your type. On the way back, an object_hook passed to json.loads() turns those dicts into your objects again.
Can I serialize Java objects without getters and setters?
Yes. Jackson can serialize public fields directly if configured with ObjectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY). However, using getters/setters is the standard Java convention and keeps encapsulation. Alternatively, use records (Java 14+) which generate canonical constructors and accessor methods automatically.
Why does JSON.stringify silently drop fields in JavaScript?
undefined values, functions, and Symbol-keyed properties are removed rather than serialized. Date objects become ISO strings and Map/Set serialize to {}. Either preprocess the object first or diff the field list against the original after stringify.
Is it safe to unpickle data from an untrusted source?
No. A pickle isn't data, it's an instruction stream — pickle.loads() runs whatever opcodes are in there, so an attacker-crafted pickle is effectively a shell on your machine. Only unpickle bytes you produced yourself; anything external goes through JSON.
Related Resources
Parse JSON in Python, JavaScript and Java
Parse JSON strings into native objects in Python, JavaScript and Java — with error handling, streaming for large files and schema validation.
RecipeParse XML Files
How to parse XML documents in Python, Java, and JavaScript with practical code examples.
RecipeParse YAML Files
How to parse YAML configuration files in Python, Java, and JavaScript.
RecipeValidate JSON Schema
How to validate JSON data against schemas in Python, Java, and JavaScript.
DocMessage Schema Evolution Policy (Avro, Protobuf, JSON)
A reusable policy for evolving message schemas safely in event-driven systems: compatibility rules, schema registry requirements, versioning, breaking change procedure, migration checklist, and CI enforcement with Avro, Protobuf, and JSON Schema examples.
PatternSchema Registry Evolution
How to manage schema versions for streaming pipelines with a schema registry. Covers Avro, backward compatibility, forward compatibility, and consumer migration.