diff --git a/Cargo.lock b/Cargo.lock
index 72ddd85..f565829 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -905,6 +905,8 @@ version = "0.0.1"
 dependencies = [
  "clap",
  "regex",
+ "serde",
+ "serde_json",
  "uucore",
 ]
 
diff --git a/src/uu/dmesg/Cargo.toml b/src/uu/dmesg/Cargo.toml
index df3ad04..96df1cc 100644
--- a/src/uu/dmesg/Cargo.toml
+++ b/src/uu/dmesg/Cargo.toml
@@ -14,3 +14,5 @@ path = "src/main.rs"
 clap = { workspace = true }
 uucore = { workspace = true }
 regex = { workspace = true }
+serde_json = { workspace = true }
+serde = { workspace = true }
diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs
index d4ae899..673b8f4 100644
--- a/src/uu/dmesg/src/dmesg.rs
+++ b/src/uu/dmesg/src/dmesg.rs
@@ -3,6 +3,8 @@ use regex::Regex;
 use std::fs;
 use uucore::error::UResult;
 
+mod json;
+
 #[uucore::main]
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let mut dmesg = Dmesg::new();
@@ -42,7 +44,7 @@ mod options {
 struct Dmesg<'a> {
     kmsg_file: &'a str,
     output_format: OutputFormat,
-    _records: Option<Vec<Record>>,
+    records: Option<Vec<Record>>,
 }
 
 impl Dmesg<'_> {
@@ -50,7 +52,7 @@ impl Dmesg<'_> {
         Dmesg {
             kmsg_file: "/dev/kmsg",
             output_format: OutputFormat::Normal,
-            _records: None,
+            records: None,
         }
     }
 
@@ -63,7 +65,7 @@ impl Dmesg<'_> {
                 records.push(Record::from_str_fields(pri_fac, seq, time, msg.to_string()));
             }
         }
-        self._records = Some(records);
+        self.records = Some(records);
         Ok(self)
     }
 
@@ -91,7 +93,18 @@ impl Dmesg<'_> {
         Ok(lines)
     }
 
-    fn print(&self) {}
+    fn print(&self) {
+        match self.output_format {
+            OutputFormat::Json => self.print_json(),
+            OutputFormat::Normal => unimplemented!(),
+        }
+    }
+
+    fn print_json(&self) {
+        if let Some(records) = &self.records {
+            println!("{}", json::serialize_records(records));
+        }
+    }
 }
 
 enum OutputFormat {
diff --git a/src/uu/dmesg/src/json.rs b/src/uu/dmesg/src/json.rs
new file mode 100644
index 0000000..b397e91
--- /dev/null
+++ b/src/uu/dmesg/src/json.rs
@@ -0,0 +1,137 @@
+use serde::Serialize;
+use std::io;
+
+pub fn serialize_records(records: &Vec<crate::Record>) -> String {
+    let json = Dmesg::from(records);
+    let formatter = DmesgFormatter::new();
+    let mut buf = vec![];
+    let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter);
+    json.serialize(&mut serializer).unwrap();
+    String::from_utf8_lossy(&buf).to_string()
+}
+
+#[derive(serde::Serialize)]
+struct Dmesg<'a> {
+    dmesg: Vec<Record<'a>>,
+}
+
+#[derive(serde::Serialize)]
+struct Record<'a> {
+    pri: u32,
+    time: u64,
+    msg: &'a str,
+}
+
+impl<'a> From<&'a Vec<crate::Record>> for Dmesg<'a> {
+    fn from(value: &'a Vec<crate::Record>) -> Self {
+        let mut dmesg_json = Dmesg { dmesg: vec![] };
+        for record in value {
+            let record_json = Record {
+                pri: record._priority_facility,
+                time: record._timestamp_us,
+                msg: &record._message,
+            };
+            dmesg_json.dmesg.push(record_json);
+        }
+        dmesg_json
+    }
+}
+
+struct DmesgFormatter {
+    nesting_depth: i32,
+}
+
+impl DmesgFormatter {
+    const SINGLE_INDENTATION: &[u8] = b"   ";
+
+    fn new() -> Self {
+        DmesgFormatter { nesting_depth: 0 }
+    }
+
+    fn write_indentation<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        for _ in 0..self.nesting_depth {
+            writer.write_all(Self::SINGLE_INDENTATION)?;
+        }
+        Ok(())
+    }
+}
+
+impl serde_json::ser::Formatter for DmesgFormatter {
+    fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        self.nesting_depth += 1;
+        writer.write_all(b"{\n")
+    }
+
+    fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        writer.write_all(b"\n")?;
+        self.nesting_depth -= 1;
+        self.write_indentation(writer)?;
+        writer.write_all(b"}")
+    }
+
+    fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        self.nesting_depth += 1;
+        writer.write_all(b"[\n")
+    }
+
+    fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        writer.write_all(b"\n")?;
+        self.nesting_depth -= 1;
+        self.write_indentation(writer)?;
+        writer.write_all(b"]")
+    }
+
+    fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        if !first {
+            writer.write_all(b",\n")?;
+        }
+        self.write_indentation(writer)
+    }
+
+    fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        writer.write_all(b": ")
+    }
+
+    fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        if first {
+            self.write_indentation(writer)
+        } else {
+            writer.write_all(b",")
+        }
+    }
+
+    fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        // The only u64 field in Dmesg is time, which requires a specific format
+        let seconds = value / 1000000;
+        let sub_seconds = value % 1000000;
+        let repr = format!("{:>5}.{:0>6}", seconds, sub_seconds);
+        writer.write_all(repr.as_bytes())
+    }
+}