Skip to main content

nu_command/formats/to/
json.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{FromValue, PipelineMetadata};
3
4#[derive(Clone)]
5pub struct ToJson;
6
7impl Command for ToJson {
8    fn name(&self) -> &str {
9        "to json"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("to json")
14            .input_output_types(vec![(Type::Any, Type::String)])
15            .switch(
16                "raw",
17                "Remove all of the whitespace and trailing line ending.",
18                Some('r'),
19            )
20            .named(
21                "indent",
22                SyntaxShape::Number,
23                "Specify indentation width.",
24                Some('i'),
25            )
26            .named(
27                "tabs",
28                SyntaxShape::Number,
29                "Specify indentation tab quantity.",
30                Some('t'),
31            )
32            .switch(
33                "serialize",
34                "Serialize nushell types that cannot be deserialized.",
35                Some('s'),
36            )
37            .category(Category::Formats)
38    }
39
40    fn description(&self) -> &str {
41        "Converts table data into JSON text."
42    }
43
44    fn run(
45        &self,
46        engine_state: &EngineState,
47        stack: &mut Stack,
48        call: &Call,
49        input: PipelineData,
50    ) -> Result<PipelineData, ShellError> {
51        let raw = call.has_flag(engine_state, stack, "raw")?;
52        let use_tabs = call.get_flag(engine_state, stack, "tabs")?;
53        let indent = call.get_flag(engine_state, stack, "indent")?;
54        let serialize_types = call.has_flag(engine_state, stack, "serialize")?;
55
56        let span = call.head;
57        // allow ranges to expand and turn into array
58        let input = input.try_expand_range()?;
59        let value = input.into_value(span)?;
60        let ty = value.get_type();
61        let json_value = value_to_json_value(engine_state, value, span, serialize_types)?;
62
63        let json_result = if raw {
64            nu_json::to_string_raw(&json_value)
65        } else if let Some(tab_count) = use_tabs {
66            nu_json::to_string_with_tab_indentation(&json_value, tab_count)
67        } else if let Some(indent) = indent {
68            nu_json::to_string_with_indent(&json_value, indent)
69        } else {
70            nu_json::to_string(&json_value)
71        };
72
73        match json_result {
74            Ok(serde_json_string) => {
75                let res = Value::string(serde_json_string, span);
76                let metadata = PipelineMetadata {
77                    data_source: nu_protocol::DataSource::None,
78                    content_type: Some(mime::APPLICATION_JSON.to_string()),
79                    ..Default::default()
80                };
81                Ok(PipelineData::value(res, Some(metadata)))
82            }
83            _ => Err(ShellError::CantConvert {
84                to_type: "JSON".into(),
85                from_type: ty.to_string(),
86                span,
87                help: None,
88            }),
89        }
90    }
91
92    fn examples(&self) -> Vec<Example<'_>> {
93        vec![
94            Example {
95                description: "Outputs a JSON string, with default indentation, representing the contents of this table.",
96                example: "[a b c] | to json",
97                result: Some(Value::test_string("[\n  \"a\",\n  \"b\",\n  \"c\"\n]")),
98            },
99            Example {
100                description: "Outputs a JSON string, with 4-space indentation, representing the contents of this table.",
101                example: "[Joe Bob Sam] | to json --indent 4",
102                result: Some(Value::test_string(
103                    "[\n    \"Joe\",\n    \"Bob\",\n    \"Sam\"\n]",
104                )),
105            },
106            Example {
107                description: "Outputs an unformatted JSON string representing the contents of this table.",
108                example: "[1 2 3] | to json -r",
109                result: Some(Value::test_string("[1,2,3]")),
110            },
111        ]
112    }
113}
114
115pub fn value_to_json_value(
116    engine_state: &EngineState,
117    v: Value,
118    call_span: Span,
119    serialize_types: bool,
120) -> Result<nu_json::Value, ShellError> {
121    let value_span = v.span();
122    match serialize_types {
123        false => nu_json::Value::from_value(v),
124        true => nu_json::Value::from_value_serialized(v, engine_state)
125    }.map_err(|err| match err {
126        ShellError::CantConvert { from_type, .. } if from_type == "closure" => ShellError::UnsupportedInput {
127            msg: "closures are currently not deserializable (use --serialize to serialize as a string)".into(),
128            input: "value originates from here".into(),
129            msg_span: call_span,
130            input_span: value_span,
131        },
132        err => err
133    })
134}
135
136#[cfg(test)]
137mod test {
138    use super::*;
139
140    #[test]
141    fn test_examples() -> nu_test_support::Result {
142        nu_test_support::test().examples(ToJson)
143    }
144}